Collections

GroupLink API

On this page

Authentication

Refer to Authentication with PowerShell for details on how to obtain an access token.

PowerShell
# Invoke interactive sign in
Add-EntraIDInteractiveUserAccessTokenProfile -Profile "Default" -TenantId "TENANTID" -ClientId "68bf2f1d-b9e1-4477-8b90-81314861f05f" -Scope https://api.fortytwo.io/.default

# Or invoke a request
Invoke-RestMethod "https://api.fortytwo.io/group-link/beta/group-links/by-collection/$CollectionId" -Headers (Get-EntraIDAccessTokenHeader -Profile "Default")

Authorization

Group Link answers to the collections roles. A link is a property of a collection, so anyone who may manage the collection may manage what it is linked to, and nothing new has to be granted.

Users

Can only be assigned to users:

RoleRole valueGranted access
Collection - Administratorcollection_criteria_definition-administratorFull access to group links

Applications

Can only be assigned to applications:

RoleRole valueGranted access
collections-criteria.criteria.read.allcollections-criteria.criteria.read.allRead links, preview, and read sync jobs
collections-criteria.criteria.readwrite.allcollections-criteria.criteria.readwrite.allThe above, plus create, update, delete and trigger a sync

Reads need read.all; anything that changes a link or starts a sync needs readwrite.all. Note that preview is a POST but only needs read.all — it changes nothing.

Endpoints

Everything is under https://api.fortytwo.io/group-link/beta, and every response is wrapped in an envelope:

JSON
{ "data": { }, "isSuccess": true, "statusCode": 200, "errors": [] }

On failure, error carries the reason and isSuccess is false.

MethodRouteDescription
POST/group-linksCreate a link. Queues its first sync.
GET/group-links/{id}One link, by link id
GET/group-links/by-collection/{collectionId}The link for a collection
PATCH/group-links/{id}Enable or disable
DELETE/group-links/{id}Delete the link
POST/group-links/previewWhat a sync would add and remove, without doing it
POST/group-links/{collectionId}/syncQueue a synchronization
GET/sync-jobs?collectionId=A collection's recent sync jobs, newest first
GET/sync-jobs/{id}One sync job
JSON
{
  "id": "6f3a1c02-4e1d-4a5f-9a9a-5b6f4d0c2e11",
  "collectionId": "a3cd66b6-b1f3-420a-a5b6-24d4dce9ee23",
  "entraGroupId": "c1e4a7d0-92b8-4a3e-bb70-2d7f1f0a94c5",
  "azureTenantId": "8f2a...",
  "createdAt": "2026-02-11T09:14:22.117+00:00",
  "state": "Enabled",
  "lastSyncedAt": "2026-02-11T10:14:35.902+00:00",
  "lastSyncOutcome": "Succeeded",
  "entraGroupDisplayName": "Finance reporting - Reader"
}

state is Enabled or Disabled; lastSyncOutcome is Succeeded or Failed, and null until the first sync finishes. entraGroupDisplayName is the group's name as it was last read — fall back to entraGroupId if it is null.

Create

JSON
{
  "collectionId": "a3cd66b6-b1f3-420a-a5b6-24d4dce9ee23",
  "entraGroupId": "c1e4a7d0-92b8-4a3e-bb70-2d7f1f0a94c5"
}

Returns the new link's id, and queues its first synchronization.

The collection and the group are both checked before the link is created, so a link that exists is a link that can work:

StatusMeaning
400collectionId or entraGroupId is empty, or the collection does not exist
403The group does not exist, cannot be written, or is a group type that cannot be linked
409That Entra ID group is already linked to a collection
502The collection or the group could not be verified — the check failed, rather than failing the check. Retry; if it persists, Identity Universe may be missing access.

Enable and disable

JSON
{ "state": "Disabled" }

A disabled link is skipped by the sweep and by reconciliation. The group keeps whatever membership it has at that point — disabling changes nothing in Entra ID, it only stops future alignment.

Preview

JSON
{
  "collectionId": "a3cd66b6-b1f3-420a-a5b6-24d4dce9ee23",
  "groupId": "c1e4a7d0-92b8-4a3e-bb70-2d7f1f0a94c5"
}

Responds with the difference between the collection and the group:

JSON
{
  "membersToAdd": ["9c1f...", "b704..."],
  "membersToRemove": ["4a12..."],
  "membersWithoutEntraAccount": 0
}

The ids are Entra ID object ids. Nothing is written.

Preview does not need an existing link, so it works as a dry run before linking an existing production group. Read membersToRemove first — on a group that already has members, that list is the risk.

membersWithoutEntraAccount is how many members of the collection have no Entra ID account to place. While it is above zero, no member will be removed.

Trigger a synchronization

POST /group-link/beta/group-links/{collectionId}/sync

Takes no body, and answers 202 Accepted with the queued job's id:

JSON
{ "jobId": "2e77b0a4-3f57-4f33-9a2c-9db35c6b4d8e" }

Accepted means queued, not finished. Poll the job to find out what happened.

Sync jobs

JSON
{
  "id": "2e77b0a4-3f57-4f33-9a2c-9db35c6b4d8e",
  "collectionId": "a3cd66b6-b1f3-420a-a5b6-24d4dce9ee23",
  "entraGroupId": "c1e4a7d0-92b8-4a3e-bb70-2d7f1f0a94c5",
  "type": "ManualSync",
  "status": "Succeeded",
  "attempts": 1,
  "added": 12,
  "removed": 3,
  "skipped": 0,
  "createdAt": "2026-02-11T10:14:30.004+00:00",
  "startedAt": "2026-02-11T10:14:31.220+00:00",
  "completedAt": "2026-02-11T10:14:35.902+00:00",
  "error": null
}
Field
typeManualSync, LinkCreated or ScheduledSweep — see when a link synchronizes
statusQueued, Running, Succeeded or Failed
attemptsA failed job is retried up to three times before it stays failed
added / removedMembers written to the group
skippedCollection members with no Entra ID account. Above zero, nothing is removed.
errorWhy it failed, when it did

GET /sync-jobs?collectionId={id} lists a collection's jobs newest first, ten by default; limit takes up to 50. This is the history — polling the job you queued and then forgetting it makes a run that changed membership invisible as soon as another follows it.

Jobs are kept for 30 days when someone triggered them, and two days for the hourly sweep.

Examples

Link a collection to a group and watch the first sync:

PowerShell
$Headers = Get-EntraIDAccessTokenHeader -Profile "Default"
$Base = "https://api.fortytwo.io/group-link/beta"

# See what it would do first
$Preview = Invoke-RestMethod "$Base/group-links/preview" -Method Post -Headers $Headers -ContentType "application/json" -Body (@{
    collectionId = $CollectionId
    groupId      = $EntraGroupId
} | ConvertTo-Json)

"Would add {0}, remove {1}, skip {2}" -f $Preview.data.membersToAdd.Count, $Preview.data.membersToRemove.Count, $Preview.data.membersWithoutEntraAccount

# Create the link - this queues the first synchronization by itself
$Link = Invoke-RestMethod "$Base/group-links" -Method Post -Headers $Headers -ContentType "application/json" -Body (@{
    collectionId = $CollectionId
    entraGroupId = $EntraGroupId
} | ConvertTo-Json)

# Follow it
Invoke-RestMethod "$Base/sync-jobs?collectionId=$CollectionId" -Headers $Headers |
    Select-Object -ExpandProperty data |
    Format-Table type, status, added, removed, skipped, completedAt

Disable a link without touching the group's current membership:

PowerShell
Invoke-RestMethod "$Base/group-links/$($Link.data)" -Method Patch -Headers $Headers -ContentType "application/json" -Body (@{ state = "Disabled" } | ConvertTo-Json)
Last updated 2 November 2026 Edit this page on GitHub
Esc