Capture the Fabric response body and errorCode first, because a 401 means token authentication or validation failed; missing workspace privileges normally produces 403, so reconfiguring Git permissions before validating the token may not address this failure.^1^
1. Capture the actual Fabric error
Replace the current Git status call temporarily with:
$uri = "https://api.fabric.microsoft.com/v1/workspaces/$workspaceId/git/status"
try {
$gitStatus = Invoke-RestMethod -Method Get -Uri $uri `
-Headers $headers -ErrorAction Stop
}
catch {
$response = $_.Exception.Response
$requestId = $response.Headers["requestId"]
$reader = [IO.StreamReader]::new($response.GetResponseStream())
$body = $reader.ReadToEnd()
Write-Error "Fabric request failed. RequestId=$requestId Body=$body"
throw
}
Fabric returns a structured error containing errorCode, message, and requestId. Preserve the requestId for support escalation.^1^
Act on the returned code:
-
TokenExpired: acquire a fresh token immediately before the API call. -
InsufficientScopes: verify that the token was requested for the Fabric API. - Another code: use that code rather than the changing human-readable message to select the next action.
2. Validate the token claims without printing the token
A JWT-shaped value only confirms its format. Check its audience, tenant, expiry, and calling application:
$payload = $token.Split('.')[1].Replace('-', '+').Replace('_', '/')
$payload += '=' * ((4 - $payload.Length % 4) % 4)
$claims = [Text.Encoding]::UTF8.GetString(
[Convert]::FromBase64String($payload)
) | ConvertFrom-Json
[pscustomobject]@{
Audience = $claims.aud
Tenant = $claims.tid
AppId = if ($claims.azp) { $claims.azp } else { $claims.appid }
Expires = [DateTimeOffset]::FromUnixTimeSeconds($claims.exp).UtcDateTime
}
Confirm that:
-
Audienceis the Fabric API audience. -
Tenantis the tenant containing the target Fabric workspace. -
AppIdis the service principal used by the Azure DevOps pipeline. -
Expiresis later than the API-call time.
The documented app-only request uses scope=https://api.fabric.microsoft.com/.default.^2^ Your Get-AzAccessToken -ResourceUrl "https://api.fabric.microsoft.com" pattern is also the documented Fabric PowerShell pattern, provided the active Az context is the intended service principal and tenant.^3^
For direct service-principal access, delegated scopes such as Workspace.GitUpdate.All are not the governing authorization mechanism; service principals are controlled through Fabric tenant controls and Fabric resource permissions.^4^
3. After token authentication succeeds, configure this SPN as the Git caller
The Git status endpoint supports service principals, but the caller must have Contributor or higher workspace access and must have its own Git credentials configured.^5^
For a workspace already connected to Azure DevOps under a user identity:
- A workspace Admin adds the SPN to the workspace. The documented existing-workspace setup uses the Admin role.
- The Azure DevOps cloud-connection owner shares that connection with the SPN through Manage users, or creates a connection using the SPN.
- Call
GET /v1/connectionsas the SPN and verify that the required connection is returned. - Configure the connection specifically for this caller:
$body = @{
source = "ConfiguredConnection"
connectionId = $connectionId
} | ConvertTo-Json
$uri = "https://api.fabric.microsoft.com/v1/workspaces/$workspaceId/git/myGitCredentials"
Invoke-RestMethod -Method Patch -Uri $uri `
-Headers $headers -Body $body -ErrorAction Stop
These are the documented steps required before that service principal performs Fabric Git REST operations.^2^
If the SPN has not been added to the Azure DevOps organization/project, an Azure DevOps administrator must add it under Organization settings → Users, then add it to the relevant project team.^6^
If the request still returns 401 after the claims are correct, provide Microsoft Support with the captured errorCode, requestId, UTC timestamp, endpoint, and non-secret claim values. Do not include the Bearer [Token] or client secret.
References