As an indirect Microsoft partner, you don’t automatically receive access to your customer’s Azure subscriptions through Partner Center.
However, you can still gain access by adding your partner tenant’s Admin Agent group (Or your GDAP security group) to their subscription. This allows you to manage it using your partner login, similar to delegated permissions in Microsoft Admin Center.
To use Sync 365’s automated Azure billing feature as an indirect provider, you must have access to your customers’ Azure subscriptions. Completing the steps below will enable automated billing to run correctly.
Get the AdminAgents (Or Security group used for GDAP) Object ID from your tenant (The MSP Tenant)
Log into your MSP tenant via https://portal.azure.com using an account with group admin privileges.
Go to Microsoft Entra> Groups.
Search for the group named "AdminAgents". (Or Security group used for GDAP))
Copy the Object ID of that group — you’ll need this for the access script.
This Object ID allows us to grant your tenant access to customer subscriptions for billing automation.

This uses substantially less access than Owner.
The customer Global Administrator cannot enable the CSP cost-visibility policy. It is controlled at the CSP billing account by the direct provider or indirect provider. If cost data is accessible but retail/RRP values are missing, enable Azure Cost Management on their subscription from the partner center or via your Distributor.

- #!/usr/bin/env bash
set -euo pipefail
if ! command -v az >/dev/null 2>&1; then
echo "Azure CLI is required. Run this in Azure Cloud Shell Bash or install Azure CLI locally." >&2
exit 1
fi
if ! az account show >/dev/null 2>&1; then
echo "No Azure CLI session found. Starting az login..."
az login >/dev/null
fi
read -r -p "Enter the AdminAgents or GDAP group Object ID: " partnerId
if [[ ! "$partnerId" =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ ]]; then
echo "The value is not a valid Object ID GUID." >&2
exit 1
fi
read -r -p "Confirm this Object ID was copied from the intended AdminAgents/GDAP group [Y/N]: " confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
echo "Cancelled." >&2
exit 1
fi
read -r -p "Attempt Azure elevateAccess first? Only needed for Global Admins without subscription RBAC [Y/N]: " elevate
if [[ "$elevate" =~ ^[Yy]$ ]]; then
az rest \
--method post \
--url "https://management.azure.com/providers/Microsoft.Authorization/elevateAccess?api-version=2015-07-01" \
>/dev/null
fi
mapfile -t subs < <(az account list --query "[?state=='Enabled'].[id,name]" -o tsv)
if (( ${#subs[@]} == 0 )); then
echo "No enabled subscriptions were found for this account." >&2
exit 1
fi
echo ""
echo "Select the subscription scope to apply Cost Management Reader access:"
echo " [0] All enabled subscriptions"
for i in "${!subs[@]}"; do
subId="${subs[$i]%%$'\t'*}"
subName="${subs[$i]#*$'\t'}"
echo " [$((i + 1))] ${subName} (${subId})"
done
read -r -p "Enter 0 for all subscriptions, or enter a subscription number: " selection
selectedIndexes=()
if [[ "$selection" == "0" ]]; then
for i in "${!subs[@]}"; do
selectedIndexes+=("$i")
done
elif [[ "$selection" =~ ^[0-9]+$ ]] && (( selection >= 1 && selection <= ${#subs[@]} )); then
selectedIndexes+=("$((selection - 1))")
else
echo "Invalid subscription selection. Enter 0 for all subscriptions, or a number from 1 to ${#subs[@]}." >&2
exit 1
fi
for i in "${selectedIndexes[@]}"; do
subId="${subs[$i]%%$'\t'*}"
subName="${subs[$i]#*$'\t'}"
scope="/subscriptions/${subId}"
az account set --subscription "$subId"
existingCount=$(az role assignment list \
--scope "$scope" \
--role "Cost Management Reader" \
--query "[?principalId=='${partnerId}'] | length(@)" \
-o tsv)
if [[ "$existingCount" == "0" ]]; then
az role assignment create \
--assignee-object-id "$partnerId" \
--assignee-principal-type ForeignGroup \
--role "Cost Management Reader" \
--scope "$scope" \
>/dev/null
fi
echo "Cost access verified: ${subName}"
done
resScope="/providers/Microsoft.Capacity"
echo ""
echo "Reservations Reader is applied to all reservations"
read -r -p "Apply or verify Reservations Reader has been applied for this group [Y/N]: " applyReservations
if [[ "$applyReservations" =~ ^[Yy]$ ]]; then
existingReservationCount=$(az role assignment list \
--scope "$resScope" \
--role "Reservations Reader" \
--query "[?principalId=='${partnerId}'] | length(@)" \
-o tsv)
if [[ "$existingReservationCount" == "0" ]]; then
az role assignment create \
--assignee-object-id "$partnerId" \
--assignee-principal-type ForeignGroup \
--role "Reservations Reader" \
--scope "$resScope" \
>/dev/null
echo "Reservations access assigned: Reservations Reader at ${resScope}"
else
echo "Reservations access already existed: Reservations Reader at ${resScope}"
fi
echo ""
echo "Reservations role assignment confirmation:"
az role assignment list \
--scope "$resScope" \
--role "Reservations Reader" \
--query "[?principalId=='${partnerId}'].{Role:roleDefinitionName,Scope:scope,PrincipalType:principalType,PrincipalId:principalId}" \
-o table
else
echo "Reservations Reader was skipped by user selection."
fi
az role assignment list \
--query "[?principalId=='${partnerId}'].{Role:roleDefinitionName,Scope:scope,PrincipalType:principalType}" \
-o table
echo "Completed. Confirm PrincipalType is showing as a ForeignGroup."