Azure resources (e.g. subscriptions) can be better protected with PIM. As a result, access roles are not granted permanently, but must be requested for a specified period of time. After this period, the permission is automatically revoked. If the PIM configuration has been activated, a review of the granted permissions can be carried out, this feature is called Access Review.

If an Access Review should to be created via REST, a Business Flow ID is required. This ID is not documented. It is generated individually and once for each subscription. If the Access Review is set up via the portal, this call is made automatically in the background. The response is an HTTP 201 Created code in whose response body the BusinessFlow ID is supplied. If the BusinessFlow ID already exists, the response is HTTP 409 Conflict and the BusinessFlow ID is contained in the text of the error message.

Response 201 Created:

{
   "@odata.context": "https://api.accessreviews.identitygovernance.azure.com/accessReviews/v2.0/$metadata#approvalWorkflowProviders('9FB60D0C-FC64-4DE9-AD80-22C1B531F505')/businessFlows/$entity",
   "id": "e7077c88-0600-4123-972e-7c4e7b1efd0a",
   "partnerId": "9fb60d0c-fc64-4de9-ad80-22c1b531f505",
   "displayName": "Subscription Id : ca91...",
   "description": "Subscription Id : ca91...",
   ...
}

Response 409 Conflict:

{
   "error": {
      "code": "",
      "message": "Business flow with id e7077c88-0600-4123-972e-7c4e7b1efd0a already exists with deDuplicationId ca91..."
   }
}

The Cmdlet Add-BusinessFlowId (see GitHub) creates or retrieves the BusinessFlowId of a subscription. The Cmdlet must be called with the SubscriptionId (line 11) and a TenantId (line 6). The Post-Body (line 14-25) needs the subscription id and the creator policy. The portal sends some more information, but it is not necessary. The response status code is stored in the variable $statusCode by line 36. Afterwards it can be used (in line 40 and 46) and the BusinessFlowId can be extracted.

[CmdletBinding()]
param (
  [parameter(Mandatory = $true)]
  [ValidateNotNullOrEmpty()]
  [String]
  $TenantId,

  [parameter(Mandatory = $true)]
  [ValidateNotNullOrEmpty()]
  [String]
  $SubscriptionId
)

$body = @{
    "deDuplicationId" = "$SubscriptionId"
    "policy" = @{
      "creatorsCriteria" = @(
        @{
          "typeId" = "DF55FFEA-AD97-49FF-A4C9-42D8341A8527"
          "role" = "Writer"
          "subscriptionId" = "$SubscriptionId"
        }
      )
    }
}

$auth=Get-AzAccessToken
$authHeader= $auth.token 
$url = "https://api.accessreviews.identitygovernance.azure.com/accessReviews/v2.0/approvalWorkflowProviders/9FB60D0C-FC64-4DE9-AD80-22C1B531F505/businessFlows?x-tenantid=$TenantId"
$flowId = $null
  $res = Invoke-RestMethod `
      -Method Post `
      -Headers @{"Authorization"="Bearer $authHeader"} `
      -ContentType "application/json; charset=utf-8" `
      -ResponseHeadersVariable respHeaders `
      -StatusCodeVariable statusCode `
      -Body (ConvertTo-Json $body -Depth 10) `
      -Uri $url `
      -SkipHttpErrorCheck
  if($statusCode -eq 409){
    $detailMsg = $res.error.message
    $pattern = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
    $allMatches = ($detailMsg | Select-String -Pattern $pattern -AllMatches)
    $flowId = $allMatches.Matches.Groups[0].Value
  }
  if($statusCode -eq 201){
    $flowId = $res.id
  }
  if(!$flowId){
    Write-Host -ForegroundColor Red "Error with $statusCode and $($res.error)" 
  }
return $flowId