15.9Examples
Last updated
The examples use Windows PowerShell 5.1, which is present on every supported Windows server. They work unchanged in PowerShell 7.
Setting up a session
$base = 'https://gateway.example.com/api'
$headers = @{
'X-API-Key' = $env:GATEWAY_API_KEY # keep the key out of the script
'X-Client-Id' = 'hr-sync'
}Tip: Store the API key in a secure location such as the Windows Credential Manager or an environment variable set for the service account, never in the script itself.
Checking connectivity
Invoke-RestMethod -Uri "$base/health"
Invoke-RestMethod -Uri "$base/metadata" -Headers $headers/health needs no credential. /metadata succeeds with any valid key, so it proves the URL and the key are right.
Searching all users in an organizational unit
$users = @()
$token = $null
do {
$query = @{ baseDn = 'OU=Staff,DC=example,DC=com'; pageSize = 500 }
if ($token) { $query.continuationToken = $token }
$page = Invoke-RestMethod -Uri "$base/ad/users" -Headers $headers -Body $query
$users += $page.data
$token = $page.meta.nextContinuationToken
} while ($page.meta.hasNextPage)
"Found $($users.Count) users"Passing a hashtable to -Body on a GET request sends it as the query string, encoded for you.
Creating a user and handling approval
# expiresAt is UTC, with or without a trailing Z. PowerShell 7 may already have
# turned it into a date; Windows PowerShell 5.1 leaves it as text.
function ConvertTo-UtcDate($value) {
if ($value -is [datetime]) {
if ($value.Kind -eq 'Unspecified') { return [datetime]::SpecifyKind($value, 'Utc') }
return $value.ToUniversalTime()
}
$styles = [Globalization.DateTimeStyles]'AssumeUniversal, AdjustToUniversal'
return [datetime]::Parse($value, [Globalization.CultureInfo]::InvariantCulture, $styles)
}
$body = @{
name = 'Jane Doe'
path = 'OU=Staff,DC=example,DC=com'
samAccountName = 'jdoe'
accountPolicy = @{ enabled = $true; changePasswordAtLogon = $true }
attributes = @{ givenName = 'Jane'; sn = 'Doe'; mail = 'jane.doe@example.com' }
} | ConvertTo-Json -Depth 5
$response = Invoke-WebRequest -Uri "$base/ad/users" -Method Post -Headers $headers `
-ContentType 'application/json' -Body $body -UseBasicParsing
$content = $response.Content | ConvertFrom-Json
if ($response.StatusCode -eq 202) {
Write-Host "Waiting for approval ($($content.reason)): $($content.reasonDetail)"
$operationId = $content.operationId
do {
Start-Sleep -Seconds 30
$op = Invoke-RestMethod -Uri "$base/operations/$operationId" -Headers $headers
$expired = $op.expiresAt -and (ConvertTo-UtcDate $op.expiresAt) -lt [datetime]::UtcNow
} while ($op.status -eq 'WaitingToApprove' -and -not $expired)
if ($op.status -eq 'Success') {
$newUserId = $op.result.entityId
} else {
throw "Create was not carried out: $($op.status) $($op.errorMessage)"
}
} else {
$newUserId = $content.objectGuid
}
"New user: $newUserId"Invoke-WebRequest is used for the write so the script can see the status code and tell 201 from 202. Both are successes as far as PowerShell is concerned; only the status code tells them apart.
Reading an error
In Windows PowerShell 5.1, a 4xx or 5xx response throws. The problem details are in the error record:
try {
Invoke-RestMethod -Uri "$base/ad/users/$id" -Method Delete -Headers $headers
} catch {
$problem = $_.ErrorDetails.Message | ConvertFrom-Json
# Problem details carry errorCode and detail; the licence and X-Client-Id refusals use code/error and message
$code = if ($problem.errorCode) { $problem.errorCode } elseif ($problem.code) { $problem.code } else { $problem.error }
$message = if ($problem.detail) { $problem.detail } else { $problem.message }
Write-Warning "$([int]$_.Exception.Response.StatusCode) ${code}: $message"
}Updating several users in one batch
$batch = @{
items = @(
@{ id = '3f2504e0-4f89-11d3-9a0c-0305e82c3301'; data = @{ replace = @{ department = 'Finance' } } }
@{ id = '9b7c1d0a-2e4f-4c55-8a1b-7f3e6d2c1a09'; data = @{ replace = @{ department = 'Finance' } } }
)
} | ConvertTo-Json -Depth 6
$response = Invoke-WebRequest -Uri "$base/ad/users/batch" -Method Patch -Headers $headers `
-ContentType 'application/json' -Body $batch -UseBasicParsing
$result = $response.Content | ConvertFrom-Json
switch ($response.StatusCode) {
200 { "All $($result.total) updated" }
207 { $result.items | Where-Object { -not $_.succeeded } |
ForEach-Object { Write-Warning "Item $($_.index): $($_.errorCode) $($_.errorMessage)" } }
202 { "Batch waiting for approval: $($result.operationId)" }
}Plain HTTP examples
Adding a user to a group:
POST /api/ad/users/3f2504e0-4f89-11d3-9a0c-0305e82c3301/groups HTTP/1.1
Host: gateway.example.com
X-API-Key: <your API key>
X-Client-Id: hr-sync
Content-Type: application/json
{ "groupId": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e" }Adding a member to a group (the member is a user, group, computer or other object, by GUID):
POST /api/ad/groups/b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e/members HTTP/1.1
Host: gateway.example.com
X-API-Key: <your API key>
X-Client-Id: hr-sync
Content-Type: application/json
{ "memberId": "3f2504e0-4f89-11d3-9a0c-0305e82c3301" }Disabling a user:
POST /api/ad/users/3f2504e0-4f89-11d3-9a0c-0305e82c3301/disable HTTP/1.1
Host: gateway.example.com
X-API-Key: <your API key>
X-Client-Id: hr-syncResetting a password:
POST /api/ad/users/3f2504e0-4f89-11d3-9a0c-0305e82c3301/reset-password HTTP/1.1
Host: gateway.example.com
X-API-Key: <your API key>
X-Client-Id: hr-sync
Content-Type: application/json
{ "newPassword": "<new password>" }A successful reset answers 204 No Content.
Good practice
- Use one API key per integration, with the narrowest role that works, so that the operations log shows which integration did what and one key can be revoked on its own.
- Always handle 202. Any write can be queued, depending on the key's supervision level and what it touches.
- Branch on
errorCode, never on message text, which may change. - Treat 207 as a partial failure and check each item.
- Respect
Retry-Afteron both202and429. - Keep keys secret and rotate them. A revoked key stops working on its next request. Keys can be given an expiry date (Chapter 12).
- Never put credentials in URLs. Send the key only in the
X-API-Keyheader, only over HTTPS.
Last updated
Thank you for your feedback.