Admin API
Die Admin API stellt Schnittstellen zum Benutzer-, Profil-, Gruppen-, Organisations- und Berechtigungs-Management zur Verfügung. Zusätzlich lassen sich Vorlagen und Snippets (Textbausteine) abfragen und verwalten. Die Endpunkte sind als versionierte REST-API unter /api/v3/{datasourceId}/Admin/... ausgelegt. Pakete werden im Web-Admin exportiert und importiert.
Authentifizierung
Um die Admin APIs aufzurufen, muss in der primedocs.config ein entsprechender Client registriert sein, siehe hierfür primedocs.config.
Nach der Registrierung kann damit ein AccessToken vom IdS angefordert werden.
Dieses PowerShell-Beispiel zeigt den Bezug des AccessTokens:
# 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
Rollen des API-Clients
Die Admin API prüft einen API-Client mit denselben Rollen wie einen angemeldeten Administrator. Welche Rollen der Client hat, legt das Attribut roles seiner Registrierung in der primedocs.config fest, siehe primedocs.config: Admin API. Ohne das Attribut erhält der Client die Standardrollen OrganisationAdmin, UserAdmin, TemplateAdmin, CampaignAdmin und SnippetAdmin, also weder SysAdmin noch Developer.
| Bereich | Erforderliche Rolle (eine davon) |
|---|---|
| Benutzer, Gruppen, AppUserGroups | SysAdmin, UserAdmin |
| Organisationseinheiten inkl. Baum, Themes und Profilrollen | SysAdmin, OrganisationAdmin |
| Vorlagen | SysAdmin, TemplateAdmin, CampaignAdmin |
| Snippets und Snippet-Gruppen | SysAdmin, SnippetAdmin |
Übersetzungsschlüssel (/Translations) | SysAdmin, OrganisationAdmin, TemplateAdmin |
Rollen (/Roles) sowie Paket-Export und -Import (/Package) | SysAdmin |
| Statistiken, Client-Nutzung, Einstellungen, Sprachen, Benutzersynchronisation, Connected Services, Profilfreigabe-Regeln | SysAdmin, Developer |
Fehlt die nötige Rolle, antwortet die API mit HTTP 403. Das Server-Log der Web API nennt dann Client-ID, Endpunkt und die erwarteten Rollen. Tokens für die Connect API oder den MCP Server weist die Admin API grundsätzlich ab. Die Endpunkte, die den Schlüssel der Datasource herausgeben, ersetzen oder damit ver- und entschlüsseln, stehen nur angemeldeten Administratoren offen, auch einem Client mit roles="SysAdmin" nicht.
Ein Client ohne roles-Attribut hat keinen Zugriff mehr auf die Bereiche, die SysAdmin oder Developer verlangen, zum Beispiel Statistiken, Client-Nutzung und Rollen. Braucht er sie, roles="SysAdmin" an seiner Registrierung ergänzen.
Query-Syntax
Die Admin API bietet typische "CRUD" (Create / Read / Update / Delete) Endpunkte als HTTP API an.
Um Daten gezielt zu suchen, bieten folgende GET-Endpunkte einen "query" Parameter an:
| HTTP GET-Endpunkt | Beschreibung |
|---|---|
/api/v3/{datasourceId}/Admin/AppUserGroups | Gibt eine Liste von "AppUserGroups" zurück. |
/api/v3/{datasourceId}/Admin/Groups | Gibt eine Liste von "UserGroups" zurück. |
/api/v3/{datasourceId}/Admin/Users | Gibt eine Liste von "Users" zurück. |
/api/v3/{datasourceId}/Admin/Templates | Gibt eine Liste von "Templates" zurück. |
/api/v3/{datasourceId}/Admin/OrganizationUnits | Gibt eine Liste von "OrganizationUnits" zurück. |
Alle diese APIs geben eine paginierte Liste der Daten zurück. Über den "Query"-Parameter kann eine Filterung/Suche ausgelöst werden.
Die Syntax setzt sich aus [Eigenschaft]:"[Filter]" [WeitereEigenschaft]:"[Weiterer Filter]"... zusammen.
" können optional um den Filter gesetzt werden, insbesondere wenn Leerzeichen Teil des Filterkriteriums sind.
Einige Eigenschaften unterstützen den *-Wildcard Operator am Anfang und Ende.
Die einzelnen Filter sind UND-verknüpft.
Beispiel:
https://{instanz}/webapi/api/v3/{datasourceId}/Admin/Templates?query=localizedName:*brief%20active:true
Sucht alle aktiven Vorlagen, deren Namen mit "brief" enden.
AppUserGroups
Mögliche "Query"-Optionen:
title: Suche nach Titel- Unterstützt den
*-Wildcard Operator am Anfang und Ende.
- Unterstützt den
primarySid: Suche nach der PrimarySid- Unterstützt den
*-Wildcard Operator am Anfang und Ende.
- Unterstützt den
Beispiel:
title:Admin primarySid:123* = Sucht alle AppUserGroups welche den Titel "Admin" tragen und mit der primarySid 123 beginnen.
UserGroups
Mögliche "Query"-Optionen:
title: Suche nach Titel- Unterstützt den
*-Wildcard Operator am Anfang und Ende.
- Unterstützt den
primarySid: Suche nach der PrimarySid- Unterstützt den
*-Wildcard Operator am Anfang und Ende.
- Unterstützt den
Beispiel:
title:Admin primarySid:123* = Sucht alle UserGroups welche den Titel "Admin" tragen und mit der primarySid 123 beginnen.
Users
Mögliche "Query"-Optionen:
title: Suche nach Titel- Unterstützt den
*-Wildcard Operator am Anfang und Ende.
- Unterstützt den
primarySid: Suche nach der PrimarySid- Unterstützt den
*-Wildcard Operator am Anfang und Ende.
- Unterstützt den
field[<User.FieldId>]: Suche nach einem konfigurierbaren Benutzerfeld (z.B.field[User.FirstName])- Unterstützt den
*-Wildcard Operator am Anfang und Ende.
- Unterstützt den
Beispiel:
title:"John*" Field[User.EmailField]:"*foobar.com" = Sucht alle Users welche im Titel am Anfang "John" stehen haben und im Benutzerfeld User.EmailField die E-Mail-Adresse mit foobar.com endet.
Templates
Mögliche "Query"-Optionen:
templateId: Suche nach der TemplateId- Muss eine GUID sein.
metaTemplateId: Suche nach der MetaTemplateId- Muss eine GUID sein.
active: Suche nach aktiven/inaktiven (gelöschten) Vorlagen- Muss ein Bool (
True,False) sein.
- Muss ein Bool (
templatestate: Suche nach dem Vorlagen Status- Unterstützt: 0 (=
InProgress), 1 (=InTest), 2 (=Released)
- Unterstützt: 0 (=
templateGroupId: Suche nach der übergeordneten Vorlagen Gruppe- Muss eine GUID sein.
localizedName: Suche nach dem Namen.- Unterstützt den
*-Wildcard Operator am Anfang und Ende.
- Unterstützt den
localizedDescription: Suche nach der Beschreibung.- Unterstützt den
*-Wildcard Operator am Anfang und Ende.
- Unterstützt den
tags: Suche nach Tags- UND-Verknüpfung über
;und ODER-Verknüpfung über|. (BeispielTAG1;TAG2;TAG3|TAG4)
- UND-Verknüpfung über
Beispiel:
active:true tags:ProjectX;Finance|Controlling = Sucht alle aktiven Templates, welche entweder getaggt sind mit ProjectX und Finance oder welche den Tag Controlling haben.
OrganizationUnits
Mögliche "Query"-Optionen:
title: Suche nach Titel- Unterstützt den
*-Wildcard Operator am Anfang und Ende.
- Unterstützt den
sid: Suche nach der SID- Unterstützt den
*-Wildcard Operator am Anfang und Ende.
- Unterstützt den
field[<Org.FieldId>]: Suche nach einem konfigurierbaren Feld der Organisationseinheit (z.B.field[Org.Title])- Unterstützt den
*-Wildcard Operator am Anfang und Ende.
- Unterstützt den
Beispiel:
title:*Sales* sid:OU-123 = Sucht alle OrganizationUnits, deren Titel "Sales" enthält und deren SID OU-123 ist.
Antwortstruktur und Paginierung
Die Listen-Endpunkte geben die Ergebnisse in einer einheitlichen Hülle zurück: Die eigentlichen Datensätze stehen unter data, die Paginierungsinformationen unter pagingDetails.
{
"data": [
{ "id": "…", "title": "…" }
],
"pagingDetails": {
"page": 1,
"pageSize": 10,
"totalPages": 5
}
}
Die Paginierung wird über die Query-Parameter page und pageSize gesteuert:
page: Standard1.pageSize: Standard10, Minimum10, Maximum100(Werte ausserhalb dieses Bereichs werden entsprechend angepasst).
Pfadangabe der Endpunkte
Die Endpunkte sind über das Routen-Präfix /api/v3/{datasourceId}/Admin/… erreichbar. In einer Standard-Installation ist die Web API unter dem Host-Pfad /webapi eingebunden. Die vollständige URL lautet daher https://{instanz}/webapi/api/v3/{datasourceId}/Admin/…. Die PowerShell-Beispiele auf dieser Seite verwenden diese vollständige Form.
Unter /Admin/ClientUsages liegt die Auswertung der eingesetzten Clients und Versionen, siehe Statistiken und Client-Nutzung.
Gemeinsame Endpunkte (/Common)
Neben /Admin/… gibt es das Routen-Präfix /api/v3/{datasourceId}/Common/… für Abfragen, die sowohl von Admin-API- als auch von Connect-API-Clients benötigt werden. Sie sind bewusst nur einmal vorhanden, damit ein bestehender Client nicht zusätzlich für den jeweils anderen Bereich registriert werden muss.
| Endpunkt | Beschreibung |
|---|---|
GET .../Common/MetaTemplates | Liefert alle MetaTemplate-IDs mit ihrem Namen als Objekt (GUID → Name). Die IDs werden für die Suche nach metaTemplateId benötigt, und zwar sowohl in der Templates-Abfrage der Admin API als auch im templateQuery der Connect API. Die Connect-Abfrage berücksichtigt dabei nur die aktuellen Vorlagentypen (siehe Vorlagen abfragen). |
GET .../Common/Principals | Sucht Security Accounts (Benutzer, Gruppen, AppUserGroups) nach Namen, gleichzeitig in der primedocs-Datenbank und im angebundenen Verzeichnis (AD, Entra ID, SCIM). Liefert pro Treffer name, primarySid, type (User, Group, AppUserGroup), isOnboardedInDataSource sowie idInDataSource (Datenbank-ID, falls bereits onboarded). Damit lässt sich eine SID für das Onboarding oder eine Berechtigungsvergabe ermitteln, ohne das Verzeichnis separat abzufragen. |
Query-Parameter von .../Common/Principals:
| Parameter | Beschreibung |
|---|---|
query (erforderlich) | Suchbegriff, mindestens 2 Zeichen. |
limit (optional) | Maximale Anzahl Treffer. Standard 20, Maximum 100. |
types (optional) | Kommaseparierte Liste der gesuchten Typen: User, Group, AppUserGroup. Standard: alle drei. |
sources (optional) | Kommaseparierte Liste der Quellen: DataSource (primedocs-Datenbank) und PrincipalConnector (angebundenes Verzeichnis). Standard: beide. |
Die /Common-Endpunkte akzeptieren ein AccessToken mit dem Scope pd_AdminWebApi oder pd_ConnectWebApi (sowie das Token der Web App). Der Bezug des Tokens ist identisch zur Authentifizierung, lediglich der Pfad unterscheidet sich. Bei einem Client-Credentials-Token (Service Account ohne Benutzerkontext) wird die Datenquelle aus dem Pfad {datasourceId} aufgelöst. Bei einem Benutzer-Token gelten dessen Datenquellen-Berechtigungen.
# Prepare headers for request
$apiRequestHeaders = @{
"Authorization" = "Bearer $accessToken"
}
# MetaTemplate-IDs abfragen
$apiUrl = "https://{instanz}/webapi/api/v3/$($datasourceId)/Common/MetaTemplates"
$metaTemplates = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
$metaTemplates.PSObject.Properties | ForEach-Object { Write-Output "$($_.Name): '$($_.Value)'" }
# Security Account suchen (nur Benutzer, nur im Verzeichnis)
$apiUrl = "https://{instanz}/webapi/api/v3/$($datasourceId)/Common/Principals?query=muster&types=User&sources=PrincipalConnector"
$principals = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
$principals | Format-Table name, primarySid, type, isOnboardedInDataSource
Beispiele
Dieses AccessToken muss bei allen Aufrufen in den Authorization-Header mitgegeben werden. Die API erlaubt u.a. das Auflisten, Anlegen und Bearbeiten von Benutzern, Profilen, Gruppen, Organisationseinheiten und Snippet-Gruppen sowie das Abfragen von Vorlagen und AppUserGroups. Nachfolgend sind Beispiele zu finden:
Benutzer auflisten
Damit werden vorhandene Benutzer aufgelistet:
# 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;
}
Benutzer-Onboarding
Über diesen Aufruf kann ein neuer Benutzer anhand seiner primarySid hinzugefügt werden. Es wird dann automatisch der Benutzer Onboarding & Offboarding Prozess durchlaufen. Gruppen werden analog über POST .../Admin/Groups?primarySid=… angelegt. Der frühere Parameter sid wird weiterhin akzeptiert. Fehlen beide, antwortet die API mit HTTP 400.
primarySid bezeichnet die SID eines Benutzers, einer Gruppe oder einer AppUserGroup aus dem Verzeichnis. Das gilt auch für Verweise darauf, etwa shareToPrimarySid und conditionIsMemberOfPrimarySid der Profilfreigabe-Regeln oder primarySid im Testlauf der Benutzersynchronisation. Nur Organisationseinheiten haben eine frei vergebene 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;
}
Berechtigungen auf Organisationseinheiten verwalten
Über die Berechtigungen einer Organisationseinheit wird gesteuert, welche Benutzer, Gruppen oder AppUserGroups die Organisationseinheit verwenden dürfen. Die Admin API bietet dafür folgende Endpunkte:
| Endpunkt | Beschreibung |
|---|---|
GET .../Admin/OrganizationUnits/{organizationUnitId} | Liefert die Details inkl. der Liste permissions. Jeder Eintrag enthält die id der Berechtigung sowie genau eine der IDs userId, groupId oder appUserGroupId. |
POST .../Admin/OrganizationUnits/{organizationUnitId}/Permissions | Fügt eine Berechtigung hinzu. Im Body genau eine der Eigenschaften UserId, GroupId oder AppUserGroupId angeben. |
DELETE .../Admin/OrganizationUnits/{organizationUnitId}/Permissions/{permissionId} | Entfernt die angegebene Berechtigung. |
Beide schreibenden Endpunkte geben die aktualisierte Organisationseinheit inkl. ihrer Berechtigungen zurück.
- Pro Security Account (Benutzer, Gruppe oder AppUserGroup) ist nur eine Berechtigung möglich. Um sie zu ändern, die bestehende Berechtigung zuerst entfernen und danach neu anlegen.
- Beim Erstellen einer Organisationseinheit (
POST .../Admin/OrganizationUnits) können Berechtigungen direkt über das optionale ArrayPermissionsmitgegeben werden (je Eintrag genau eine der drei IDs, jeder Account höchstens einmal).
# 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
Textbaustein-Metadaten verwalten
Neben den Snippet-Gruppen lassen sich auch die Metadaten einzelner Textbausteine über die Admin API lesen und pflegen. Darauf baut das Textbaustein-Management im Web-Admin auf. Verwaltet werden dabei ausschliesslich die Metadaten (Name, Schlüssel, Sortierung, AutoText, Berechtigungen und Lokalisierungen). Inhalt und Format eines Textbausteins werden über diese Endpunkte nur gelesen, nicht verändert.
Die Endpunkte liegen unter .../Admin/Snippets/{usage}/…. {usage} grenzt den Bereich ein und ist entweder public (allgemein verfügbare Textbausteine) oder designer (Textbausteine der Vorlagenerstellung).
| Endpunkt | Beschreibung |
|---|---|
GET .../Admin/Snippets/{usage}/{snippetId} | Liefert einen Textbaustein mit Metadaten, Lokalisierungen (localized), Berechtigungen (permissions) und den vorhandenen Inhalts- und Formatangaben (data, schreibgeschützt). |
POST .../Admin/Snippets/{usage} | Legt einen neuen, rein metadatenbasierten Textbaustein an. |
PUT .../Admin/Snippets/{usage}/{snippetId} | Ändert die Metadaten (SnippetGroupId, Sort, Key, AutoTextName). |
DELETE .../Admin/Snippets/{usage}/{snippetId} | Löscht den Textbaustein. |
POST .../Admin/Snippets/{usage}/{snippetId}/Permissions | Fügt eine Berechtigung hinzu (im Body genau eine der IDs UserId, GroupId oder AppUserGroupId, dazu HasModifyPermission/HasUsingPermission). |
DELETE .../Admin/Snippets/{usage}/{snippetId}/Permissions/{permissionId} | Entfernt die angegebene Berechtigung. |
POST .../Admin/Snippets/{usage}/{snippetId}/Localized | Fügt die Lokalisierung für eine Sprache hinzu (Name, Description, SearchKeywords, Lcid). |
DELETE .../Admin/Snippets/{usage}/{snippetId}/Localized/{lcid} | Entfernt die Lokalisierung der angegebenen Sprache (lcid). |
Alle Endpunkte geben den aktualisierten Textbaustein zurück. Es gelten u.a. folgende Regeln:
Keyist optional, maximal 256 Zeichen und über alle Textbausteine hinweg eindeutig.AutoTextNameist optional und maximal 32 Zeichen.Sortist ≥ 0 und innerhalb derselben übergeordneten Gruppe eindeutig. Eine leereSnippetGroupIdordnet den Textbaustein direkt der obersten Ebene zu.- Pro Sprache (
Lcid) ist nur eine Lokalisierung möglich, und pro Lokalisierung muss mindestens einer der WerteName,DescriptionoderSearchKeywordsgesetzt sein. - Pro Security Account (Benutzer, Gruppe oder AppUserGroup) ist nur eine Berechtigung möglich.
Diese Endpunkte erfordern die Rolle SysAdmin oder SnippetAdmin. Für einen API-Client gilt die Rolle aus seiner Registrierung, siehe Rollen des API-Clients.
$apiRequestHeaders = @{
"Authorization" = "Bearer $accessToken"
"Content-Type" = "application/json"
}
# Metadaten eines Textbausteins lesen
$apiUrl = "https://{instanz}/webapi/api/v3/$($datasourceId)/Admin/Snippets/public/$($snippetId)"
$snippet = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
$snippet.localized | Format-Table
# Metadaten ändern (z.B. Schlüssel und Sortierung)
$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
Farb- und Schriftthemen einer Organisationseinheit
Die Farbthemen und Schriftthemen einer Organisationseinheit lassen sich als XML lesen und setzen. Alle Endpunkte beziehen sich auf die Standard-Dokumentsprache der Datasource.
| Endpunkt | Beschreibung |
|---|---|
GET .../Admin/OrganizationUnits/{organizationUnitId}/Themes | Liefert die wirksamen Themen unter color und font, jeweils mit xml, isInherited und sourceOrganizationUnitId (die Einheit, von der das Thema stammt). |
PUT .../Admin/OrganizationUnits/{organizationUnitId}/Themes/Color | Setzt das Farbthema. Body: { "xml": "<ColorThemes>…</ColorThemes>" }. Ein geerbtes Thema wird damit übersteuert. |
PUT .../Admin/OrganizationUnits/{organizationUnitId}/Themes/Font | Setzt das Schriftthema, Body analog. |
DELETE .../Admin/OrganizationUnits/{organizationUnitId}/Themes/Color | Entfernt das eigene Farbthema. Die Einheit erbt danach wieder von der übergeordneten Einheit, eine Wurzeleinheit hat dann keines. |
DELETE .../Admin/OrganizationUnits/{organizationUnitId}/Themes/Font | Entfernt das eigene Schriftthema. |
Alle Endpunkte geben die wirksamen Themen nach der Änderung zurück. Das XML wird vor dem Speichern geprüft. Ungültiges XML führt bei PUT zu HTTP 400, ein ungültig gespeichertes Thema bei GET zu HTTP 422, eine unbekannte Organisationseinheit zu HTTP 404, jeweils als ProblemDetails mit code. Gleichzeitige Änderungen überschreiben sich, die letzte gewinnt.
Übersetzungsschlüssel
GET .../Admin/Translations liefert alle aktiven Schlüssel der globalen Übersetzungen mit ihrem Wert in der Standard-Oberflächensprache der Datasource. Fehlt dort ein Wert, steht der Wert der ersten Sprache, die einen hat. Die Antwort enthält uiLcid und unter keys pro Schlüssel key, value und lcid (die Sprache des gemeldeten Werts, null ohne jeden Wert).
Statistiken
Die Statistiken werten das Nutzungsprotokoll aus. Als Dokumenterstellung zählt jeder Eintrag «Vorlage verwendet» (Open).
| Endpunkt | Beschreibung |
|---|---|
GET .../Admin/Statistics/DocumentCreations | Anzahl erstellter Dokumente pro Tag oder Woche (buckets, Zeiträume ohne Erstellung mit 0), dazu totalCount und die bis zu 10 meistverwendeten Vorlagen (topTemplates). |
GET .../Admin/Statistics/TemplateUsages | Pro Vorlage über alle Versionen: Anzahl erstellter Dokumente (openCount), Anzahl Erstellungen, Änderungen und Löschungen der Vorlage, Zeitpunkt des letzten Protokolleintrags der Vorlage im Zeitraum (lastUsedUtc, jede Aktion) sowie die Aufteilung nach Organisationseinheit und Dokumentsprache. Dazu unter organizationUnits pro Einheit die Anzahl Dokumente und verwendeter Vorlagen. |
| Parameter | Beschreibung |
|---|---|
fromUtc | Beginn des Zeitraums (inklusive, UTC). Standard: 12 Wochen vor dem Ende. |
toUtc | Ende des Zeitraums (exklusive, UTC). Standard: jetzt. Der Zeitraum darf höchstens 366 Tage umfassen. |
interval | Nur DocumentCreations: Day oder Week (Standard, Wochen beginnen am Montag). |
uiLanguage | Sprache der Vorlagen- und Gruppennamen als IETF-Tag, zum Beispiel de oder fr-CH. Ohne Übersetzung gilt die Standard-Oberflächensprache. |
Jede Vorlage trägt einen status: Active (mindestens eine Version aktiv), Deleted (alle Versionen gelöscht, aber noch in der Datenbank) oder Removed (nicht mehr in der Datenbank, der Name stammt aus dem Protokoll). Einträge ohne Organisationseinheit erscheinen unter einer leeren ID.
Die Auswertung der eingesetzten Clients und Versionen liegt unter GET .../Admin/ClientUsages/Summary und GET .../Admin/ClientUsages. Die Zusammenfassung enthält neben den Einträgen pro Client-Typ und Version unter activeUsers die Anzahl aktiver Benutzer der letzten 7, 30 und 60 Tage. Details unter Client-Nutzung.
Einstellungen
Die Einstellungen der Datasource liegen unter .../Admin/Settings/…. Jeder Bereich hat ein GET zum Lesen und ein PUT zum Schreiben:
| Bereich | Inhalt |
|---|---|
General | Akzentfarbe des Clients, Hilfe-Link und Hilfe-Titel, Snippet-Tastenkürzel (XML) |
ClientSync | Synchronisationsintervall und maximale Verzögerung nach dem Start von primedocs Desktop. POST .../ClientSync/CacheReset weist alle Clients an, ihren Cache beim nächsten Start zu verwerfen. |
Security | privacyLogUserData und logEntryRetentionDays für das Nutzungsprotokoll, dazu ob ein Schlüssel der Datasource existiert und dessen Kurzform (Fingerprint) |
PrincipalConnector | Aktiver Verzeichnis-Konnektor, Erreichbarkeit, Einstellungen pro Konnektor (Active Directory, Microsoft Graph, SCIM mit tenantName aus der Serverkonfiguration) und die Regel für den Anzeigenamen in userNameRule |
Outlook, Connect, Ribbon, Ai | Die Einstellungen des jeweiligen Bereichs |
ProfileOrganizationValidation | Modus der Profil-Validierung und Zeitpunkt des letzten Laufs |
Die Regel für den Anzeigenamen wird separat gespeichert: PUT .../Settings/PrincipalConnector/UserNameRule mit expression (Bausteine in parts und separator oder ein JavaScript in script, null entfernt die Regel) und fallbackAttribute (Namensattribut für leere Ergebnisse). POST .../Settings/PrincipalConnector/UserNameRule/Preview berechnet einen Entwurf für einen Benutzer (primarySid) ohne zu speichern. Mit blankAttributes lassen sich leere Attribute simulieren.
Den Schlüssel der Datasource selbst (Security/EncryptionKey) sowie Security/Encrypt und Security/Decrypt beantwortet die API nur für angemeldete Administratoren, nicht für API-Clients.
Felder
Die Felddefinitionen der Benutzer und der Organisationseinheiten liegen unter .../Admin/Fields/User und .../Admin/Fields/OrganizationUnit, siehe Felder im Web-Admin. Erforderlich ist die Rolle SysAdmin.
| Endpunkt | Beschreibung |
|---|---|
GET .../Fields/User | Alle Benutzerfelder |
POST .../Fields/User | Legt ein Feld an |
PUT .../Fields/User/{id} | Ändert ein Feld. revision aus der letzten Antwort mitsenden, sonst antwortet die API bei zwischenzeitlichen Änderungen mit 409 Conflict. |
DELETE .../Fields/User/{id}?revision=… | Löscht ein inaktives Feld samt allen Werten |
PUT .../Fields/User/Layout | Ändert die Anordnung mehrerer Felder in einem Aufruf: fields mit id, revision, row, column, sort und optional group |
Dieselben Endpunkte gibt es unter .../Fields/OrganizationUnit. Ein Feld enthält fieldId, type (text, image, checkbox), active, row, column, sort, isRequired, canBeModifiedInProfile, multiline, multilineHeight, textLength, imageType und localizations (pro lcid mit title, group, description, initialValue). Benutzerfelder haben zusätzlich canBeModifiedInUser und externalReference. fieldId und type lassen sich nach dem Anlegen nicht mehr ändern. Felder für Farb- und Schriftthemen liefert die API nicht.
SID-Migration
Unter .../Admin/PrimarySidMigration/… liegen die Endpunkte des Verzeichniswechsels. Sie verknüpfen bestehende Benutzer und Gruppen mit ihrer SID im aktuell konfigurierten Verzeichnis. Erforderlich ist die Rolle SysAdmin. Alle Endpunkte verwenden POST:
| Endpunkt | Beschreibung |
|---|---|
Users/Preview | Gleicht alle Benutzer über einen gemeinsamen Wert mit dem Verzeichnis ab und schlägt die SID aus dem Verzeichnis vor. Body: field (Id eines aktiven Benutzer-Textfelds, z.B. User.Email) und matchingProperty (Eigenschaft im Verzeichnis, z.B. mail). Schreibt nichts. |
Groups/Preview | Gleicht alle Gruppen über ihren Namen ab. Ohne Body. Schreibt nichts. |
Users/Apply, Groups/Apply | Schreibt die bestätigten SIDs. Body: entries mit id, newPrimarySid und expectedPrimarySid (SID aus der Vorschau). |
Die Vorschau liefert pro Konto unter anderem currentPrimarySid, proposedPrimarySid, status (Found, ToMigrate, NotFound) sowie bei Konflikten suggestion (Preferred, Parked, Undecided), suggestionReason und placeholderPrimarySid. Ist das Verzeichnis nicht erreichbar, steht das in directoryAvailable und directoryError.
Die Antwort von Apply enthält applied (mit oldPrimarySid und newPrimarySid) und rejected (mit code und message). Gültige Einträge werden auch dann geschrieben, wenn andere zurückgewiesen werden, rejected muss also immer ausgewertet werden. Ändern sich die Konten während des Laufs, antwortet die API mit 409 Conflict und schreibt nichts.
Suche
GET .../Admin/Search ist die Suche hinter der Suchleiste des Web-Admin. Sie findet in der Datasource Benutzer, Gruppen, App-Gruppen, Organisationseinheiten, Profil-Rollen sowie Benutzer- und Organisationsfelder über Anzeigename, technische ID oder SID und Übersetzungen. Erforderlich ist eine der Rollen SysAdmin, OrganisationAdmin oder UserAdmin. Die Rollen bestimmen auch, welche Objekttypen durchsucht werden: Benutzer, Gruppen und App-Gruppen für UserAdmin, Organisationseinheiten und Profil-Rollen für OrganisationAdmin, Felder nur für SysAdmin.
| Parameter | Beschreibung |
|---|---|
query (erforderlich) | Suchbegriff, mindestens 2 Zeichen nach dem Entfernen von Leerzeichen am Rand. Kürzere Begriffe liefern eine leere Liste. |
lcid (optional) | Sprache der Anzeigenamen, Standard 1033. Fehlt eine Übersetzung in dieser Sprache, gilt die nächstliegende. |
limit (optional) | Maximale Anzahl Treffer je Objekttyp. Standard 5, zulässig 1 bis 20. |
Die Antwort ist eine Liste von Treffern mit id, name, type (User, Group, AppUserGroup, OrganizationUnit, ProfileRole, UserField, OrganizationUnitField) und description. Die Beschreibung nennt die technische Kennung, etwa die SID eines Kontos oder die ID einer Rolle oder eines Felds. Wurde ein Feld über seine Gruppe gefunden, steht statt der Feld-ID die Gruppe. Innerhalb eines Objekttyps stehen exakte Treffer vor Treffern am Wortanfang und diese vor Treffern innerhalb des Namens. Das Limit gilt nach dieser Sortierung, ein exakter Treffer geht also nicht verloren.
# 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=2055&limit=5"
$hits = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $apiRequestHeaders
$hits | Format-Table type, name, description, id
PowerShell Script
Nachfolgend finden Sie ein vollständiges Beispiel-PowerShell-Script, welches verschiedene UseCases abdeckt: Benutzer, Profile und Profile-Shares, Gruppen, Organisationseinheiten, Snippet-Gruppen (inkl. Berechtigungen und Lokalisierungen), AppUserGroups sowie Templates.
Der Abschnitt Configuration (datasourceId, tokenUrl, baseApiUrl, clientId und clientSecret) muss auf die Einstellungen der jeweiligen Umgebung angepasst werden.
# 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')
Fehlerbehandlung
Die HTTP-Statuscodes und das Fehler-Response-Format sind einheitlich unter API → Fehlerbehandlung beschrieben. Fehlende Rechte (Rolle SysAdmin bzw. OrganisationAdmin) ergeben 403 Forbidden, eine unbekannte datasourceId ergibt 404 Not Found.
Zusätzlich gilt für die Admin API:
- Gleichzeitige Änderung: Wurde ein Datensatz während des Speicherns von jemand anderem geändert oder gelöscht, antwortet die API mit
409 ConflictalsProblemDetails(application/problem+json) mitcodeConcurrentModification. Es wurde nichts gespeichert. Den Datensatz neu laden und den Aufruf wiederholen. - Benutzer und Gruppen löschen: Der aufrufende Benutzer muss jede Rolle des Zielkontos selbst besitzen, direkt oder über App-Gruppen, sonst antwortet die API mit
403 Forbidden. Dasselbe gilt für Änderungen an Mitgliedern, Regel oder Ausführung einer App-Gruppe.SysAdmindarf immer, die GrundrolleUserzählt nicht. Den letzten Inhaber der RolleSysAdminlehnt die API mit400 Bad Requestab. - Zeitstempel werden als UTC ausgegeben.
Swagger / Open API
Die aktuelle Admin API Open API kann z.B. von der PrimeSoft Instanz als Referenz genutzt werden:
https://primesoft.primedocs.io/webapi/swagger/index.html
Sie können diese Beschreibung z.B. über den Swagger Editor visualisieren.