> ## Documentation Index
> Fetch the complete documentation index at: https://help.nops.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Microsoft Azure - Prerequisites

> Understand billing account types, locate your Management Group ID, and prepare your Azure environment for nOps onboarding.

## FAQs

<AccordionGroup>
  <Accordion title="What is the difference between EA and MCA billing accounts?">
    * **Enterprise Agreement (EA)**: A contract between your organization and Microsoft for large-scale Azure usage. EA billing accounts use numeric enrollment numbers (e.g., `12345678`).
    * **Microsoft Customer Agreement (MCA)**: A newer billing model available through Microsoft sales representatives or the Azure portal. MCA billing accounts use GUID-based IDs (e.g., `5e98e158-xxxx-xxxx-xxxx-xxxxxxxxxxxx:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_2019-05-31`).

    If you are unsure which type you have, go to [Cost Management + Billing](https://portal.azure.com/#blade/Microsoft_Azure_GTM/ModernBillingMenuBlade) in the Azure Portal and check the **Properties** of your billing account.
  </Accordion>

  <Accordion title="What is a Management Group?">
    A Management Group is a container in Azure that helps you manage access, policies, and compliance across multiple subscriptions. All subscriptions within a management group automatically inherit the conditions applied to the management group, including RBAC role assignments.

    nOps assigns roles at the management group scope so that all subscriptions underneath are automatically covered.
  </Accordion>

  <Accordion title="Why does nOps require admin consent?">
    nOps uses a **multi-tenant Azure application**. When you grant admin consent, Microsoft creates a **service principal** in your Azure tenant. This service principal is the identity that nOps uses to access your Azure resources. No credentials are stored -- the service principal is managed entirely by Azure AD.
  </Accordion>

  <Accordion title="Can I use the root management group?">
    Yes. If you want nOps to have visibility across **all** subscriptions in your tenant, use your **Tenant ID** as the Management Group ID. The root management group in Azure always matches the Tenant ID.
  </Accordion>
</AccordionGroup>

***

## Overview

Integrate your **Microsoft Azure** environment with the nOps platform for comprehensive cost tracking, optimization recommendations, and commitment management. This integration uses Azure's native RBAC system and FOCUS cost exports to provide full visibility into your Azure spending.

nOps supports two onboarding tiers:

1. **[Savings Analysis](/azure/integration-setup)** -- Read-only cost visibility, resource inventory, and optimization recommendations
2. **[Commitment Management](/azure/commitment-management-setup)** -- Automated purchasing and management of Azure Reservations and Savings Plans (requires Savings Analysis first)

***

## What You'll Need

* **Azure AD Global Administrator** or **Privileged Role Administrator** access -- to grant admin consent for the nOps application
* **Azure RBAC Owner** or **User Access Administrator** role on the target management group or subscription -- to assign roles to nOps (via Portal or Terraform)
* **MCA only — Savings Analysis:** **Billing Account Owner** on the MCA billing account -- to assign the **Billing Account Reader** role to the nOps service principal. This is a billing-scope role, separate from Entra ID and Azure RBAC. Find who holds this role in [**Cost Management + Billing**](https://portal.azure.com/#blade/Microsoft_Azure_GTM/ModernBillingMenuBlade) → select billing account → **Access control (IAM)**.
* **MCA only — Commitment Management:** **Billing Profile Owner** on the relevant MCA billing profile -- to assign the **Billing Profile Contributor** role to the nOps service principal. This is a billing-scope role, separate from Entra ID and Azure RBAC. Find who holds this role in [**Cost Management + Billing**](https://portal.azure.com/#blade/Microsoft_Azure_GTM/ModernBillingMenuBlade) → select billing account → **Billing profiles** → select profile → **Access control (IAM)**.
* Your **Management Group ID** -- determines the scope of nOps visibility
* An **Azure Subscription** -- nOps will create a storage account and FOCUS cost export in this subscription
* Knowledge of your **Billing Account Type** -- Enterprise Agreement (EA) or Microsoft Customer Agreement (MCA)

**Additional requirement for Commitment Management:**

* A **dedicated Azure subscription** for nOps reservation and savings plan purchases

<Warning>
  **Entra ID roles ≠ Azure RBAC roles ≠ billing-scope roles.** Being a Global Administrator in Microsoft Entra ID does **not** automatically grant permissions on Azure resources (subscriptions, management groups), and neither Entra ID nor Azure RBAC Owner grants billing-scope roles such as **Billing Account Owner** or **Billing Profile Owner**. For MCA accounts, the billing role assignment in Step B must be completed by a Billing Account Owner (SA) or Billing Profile Owner (CM). If you are a Global Admin but have never managed Azure resources, follow the steps below to enable Azure RBAC access.
</Warning>

***

## Enabling Azure RBAC Access for Global Admins

Azure has two separate permission systems that are commonly confused:

| System                       | What It Controls                                              | Example Roles                            |
| ---------------------------- | ------------------------------------------------------------- | ---------------------------------------- |
| **Microsoft Entra ID roles** | Identity, directory, and app registration management          | Global Administrator, User Administrator |
| **Azure RBAC roles**         | Azure resource operations (subscriptions, VMs, storage, etc.) | Owner, Contributor, Reader               |

The nOps setup requires **Azure RBAC permissions** (Owner or User Access Administrator) to assign roles to the nOps service principal. If you are a Global Administrator without Azure RBAC roles, you'll need to enable elevated access.

<Steps>
  <Step title="Enable Elevated Access">
    1. Go to [**Microsoft Entra ID > Properties**](https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/Properties) in the Azure Portal
    2. Find **"Access management for Azure resources"**
    3. Toggle to **Yes** and click **Save**

    This grants your Global Admin account the `User Access Administrator` role at the root scope, which cascades to all management groups and subscriptions.
  </Step>

  <Step title="Sign Out and Sign Back In">
    You must sign out of the Azure Portal and sign back in to pick up the new `User Access Administrator` role:

    1. Click your profile picture in the top-right corner of the Azure Portal
    2. Click **Sign out**
    3. Navigate back to [portal.azure.com](https://portal.azure.com) and sign in again

    <Info>
      Azure uses Continuous Access Evaluation (CAE) which may invalidate cached tokens when permissions change. After signing back in, if you still see permission errors, wait 2-3 minutes for the role to fully propagate.
    </Info>
  </Step>

  <Step title="Assign Yourself Owner on the Subscription">
    The `User Access Administrator` role only allows managing role assignments. You also need `Owner` to register resource providers and manage resources:

    1. Go to [**Subscriptions**](https://portal.azure.com/#view/Microsoft_Azure_Billing/SubscriptionsBlade) in the Azure Portal
    2. Select the subscription where you'll run the nOps setup
    3. Click **Access control (IAM)** in the left menu
    4. Click **Add** > **Add role assignment**
    5. In the **Role** tab, search for and select **Owner**
    6. In the **Members** tab, click **+ Select members**
    7. Search for your email address and select your user account
    8. Click **Select**, then **Review + assign**
  </Step>

  <Step title="Verify Access">
    To confirm you now have the necessary permissions:

    1. Go to the subscription you just assigned Owner to
    2. Click **Access control (IAM)** in the left menu
    3. Click **View my access** (top button)
    4. You should see both **User Access Administrator** (inherited from root) and **Owner** (subscription-level) listed

    <Info>
      The first time you access Management Groups or run certain operations, Azure may automatically register resource providers like `Microsoft.Management`. This one-time operation can take 30-60 seconds.
    </Info>
  </Step>
</Steps>

<Info>
  For more details, see Microsoft's documentation on [Elevating access to manage all Azure subscriptions](https://learn.microsoft.com/en-us/azure/role-based-access-control/elevate-access-global-admin).
</Info>

***

## 1. Understanding Billing Account Types

Your billing account type determines how nOps configures billing access. You must select the correct type during onboarding. **Follow only the path that matches your agreement** — EA and MCA are two separate setup paths.

<Info>
  **Supported Billing Account Types:** nOps supports **Enterprise Agreement (EA)** and **Microsoft Customer Agreement (MCA)** billing accounts only. If you have a Pay-As-You-Go (MOSP) subscription, please contact your Microsoft account representative to upgrade to an EA or MCA billing account before proceeding.
</Info>

### Path A: If you have an Enterprise Agreement (EA)

Use this path if your organization has a direct **Enterprise Agreement** with Microsoft.

**What you need:**

* **EA Enrollment Number** — A numeric ID (e.g., `12345678`) that identifies your EA billing account. You will enter this in the nOps wizard in **Get Started** (and can edit it later in **Configure**).
* **Where to find it:** Go to [**Cost Management + Billing**](https://portal.azure.com/#blade/Microsoft_Azure_GTM/ModernBillingMenuBlade) in the Azure Portal, select your EA billing account, and copy the enrollment number shown at the top.
* **Billing hierarchy:** Enrollment → Departments → Accounts → Subscriptions. nOps uses the enrollment (billing account) scope for role assignment and FOCUS exports.
* **How to confirm:** In Cost Management + Billing, open your billing account → **Settings > Properties**. The **Agreement type** field must show **"Enterprise Agreement"**.

### Path B: If you have a Microsoft Customer Agreement (MCA)

Use this path if your organization has a **Microsoft Customer Agreement** (the newer billing model, often via Microsoft sales or the Azure portal).

**What you need:**

* **Billing Account** — Your MCA billing account (GUID-style ID). You do **not** need to enter a numeric enrollment number; the nOps wizard will use your billing account and profiles.
* **Where to find it:** Go to [**Cost Management + Billing**](https://portal.azure.com/#blade/Microsoft_Azure_GTM/ModernBillingMenuBlade) in the Azure Portal and select your **Microsoft Customer Agreement** billing account. For Commitment Management, you will assign roles at the **Billing Profile** level.
* **Billing hierarchy:** Billing Account → Billing Profiles → Invoice Sections → Subscriptions.
* **How to confirm:** In Cost Management + Billing, open your billing account → **Settings > Properties**. The **Agreement type** field must show **"Microsoft Customer Agreement"**.

### Quick reference: EA vs MCA

| Feature                   | Enterprise Agreement (EA)                           | Microsoft Customer Agreement (MCA)                                    |
| ------------------------- | --------------------------------------------------- | --------------------------------------------------------------------- |
| **ID Format**             | Numeric enrollment number (e.g., `12345678`)        | GUID format (e.g., `5e98e158-xxxx-..._2019-05-31`)                    |
| **Billing Hierarchy**     | Enrollment > Departments > Accounts > Subscriptions | Billing Account > Billing Profiles > Invoice Sections > Subscriptions |
| **Billing Role for nOps** | EnrollmentReader                                    | Billing Account Reader                                                |
| **How to Check**          | Properties show "Enterprise Agreement"              | Properties show "Microsoft Customer Agreement"                        |

***

## 2. Verifying You Do NOT Have a CSP Subscription

<Warning>
  **Cloud Solution Provider (CSP) subscriptions are NOT supported.** nOps only works with direct Microsoft agreements (EA or MCA). If your Azure account is through a reseller or partner using the CSP program, you cannot use nOps.
</Warning>

A **Cloud Solution Provider (CSP)** is a Microsoft partner who resells Azure services. CSP subscriptions are billed through the partner, not directly by Microsoft. This creates a different billing structure that nOps does not currently support.

### Recommended: run the nOps onboarding qualifier

The fastest way to confirm eligibility is the **nOps onboarding qualifier**, a read-only script that groups your subscriptions by billing account, classifies each as **EA / MCA / Reseller / CSP**, shows month-to-date spend, and prints an overall verdict.

**What it does, and does not do**

* It is completely read-only. It does not create, modify, deploy, or configure anything in your environment, and it does not grant nOps any access.
* It reads only billing metadata (the billing account, profile, and agreement type each subscription belongs to) and month-to-date spend per subscription.
* Nothing is transmitted anywhere. The script prints its results to your screen, and you decide what to share.

<Info>
  For spend figures, the account you sign in with needs **Cost Management Reader** (or **Reader**) at subscription scope. Without it, those subscriptions show `n/a` instead of a number, and the EA / MCA / CSP classification still works.
</Info>

**Run it in Azure Cloud Shell (about two minutes)**

Cloud Shell already has the Azure CLI and `jq` installed and is signed in as you, so there is nothing to install and no separate login.

<Steps>
  <Step title="Sign in, then open Cloud Shell">
    Sign in to the [Azure portal](https://portal.azure.com) with an account that can view your billing (ideally whoever manages billing for your organization), confirm your organization's directory is selected (top right), then open **Cloud Shell** with the `>_` icon in the top toolbar. Choose **Bash** if prompted.
  </Step>

  <Step title="Add the script">
    Upload `nops-onboarding-qualifier.sh` (**Manage files → Upload**), or copy the script below and paste it into Cloud Shell.
  </Step>

  <Step title="Run it">
    Run `bash nops-onboarding-qualifier.sh`. It finishes in a few seconds. Add `--csv results.csv` to also write a CSV, `--last-month` for the previous month's spend, or `--help` for all options.
  </Step>
</Steps>

<Tip>
  Prefer your own terminal? With the [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) and [jq](https://jqlang.github.io/jq/download/) installed, run `az login` and then `bash nops-onboarding-qualifier.sh`.
</Tip>

<Accordion title="nops-onboarding-qualifier.sh (click to expand)">
  ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
  #!/usr/bin/env bash
  # === nOps onboarding qualifier ===
  # Groups subscriptions by billing account -> profile, classifies each account
  # EA / MCA / Reseller / CSP, shows spend, gives an onboard verdict. Read-only.
  # Best run as the customer's billing admin, in their tenant.
  #
  # Hardened: portable (no md5sum), honest failures (n/a vs 0.00), preflight
  # checks, RBAC caveat, safe arg parsing, mixed-currency flagging.
  set -u
  CSV=""; SKIP_COST=0; WINDOW="MonthToDate"
  while [ $# -gt 0 ]; do case "$1" in
   --csv) [ $# -ge 2 ] || { echo "--csv needs a FILE argument" >&2; exit 1; }; CSV="$2"; shift 2;;
   --last-month) WINDOW="TheLastMonth"; shift;;
   --skip-cost) SKIP_COST=1; shift;;
   -h|--help) echo "Usage: $0 [--csv FILE] [--last-month] [--skip-cost]"; exit 0;;
   *) echo "unknown option: $1" >&2; exit 1;; esac; done

  command -v jq >/dev/null || { echo "jq not found — install jq and retry" >&2; exit 1; }
  command -v az >/dev/null || { echo "az (Azure CLI) not found — install it and run 'az login'" >&2; exit 1; }
  az account show >/dev/null 2>&1 || { echo "not logged in — run 'az login' first" >&2; exit 1; }

  D=$(mktemp -d) || { echo "mktemp -d failed" >&2; exit 1; }; trap 'rm -rf "$D"' EXIT; : > "$D/rows"; mkdir -p "$D/pc"

  echo "Signed in as : $(az account show --query user.name -o tsv 2>/dev/null)"
  echo "Tenant       : $(az account show --query tenantId -o tsv 2>/dev/null)"
  echo "Note         : costs need Cost Management Reader (or Reader) at subscription scope;"
  echo "               subscriptions without it show 'n/a', not 0.00."
  echo "Qualifying..."

  # billing accounts the signed-in identity has a billing role on (visibility flag)
  az rest --method get \
   --url "https://management.azure.com/providers/Microsoft.Billing/billingAccounts?api-version=2024-04-01" 2>/dev/null \
   | jq -r '.value[]?.name' > "$D/visible" || true

  BODY="{\"type\":\"ActualCost\",\"timeframe\":\"$WINDOW\",\"dataset\":{\"granularity\":\"None\",\"aggregation\":{\"totalCost\":{\"name\":\"Cost\",\"function\":\"Sum\"}}}}"

  az account list --all --query "[?state=='Enabled'].{i:id,n:name}" -o tsv | while IFS=$'\t' read -r SUBID SUBNM; do
   BP=$(az rest --method get \
    --url "https://management.azure.com/subscriptions/$SUBID/providers/Microsoft.Billing/billingProperty/default?api-version=2024-04-01" 2>/dev/null)
   AG=$(echo "$BP" | jq -r '.properties.billingAccountAgreementType // ""')
   BA=$(echo "$BP" | jq -r '.properties.billingAccountDisplayName // "NO BILLING ACCESS"')
   BAID=$(echo "$BP" | jq -r '.properties.billingAccountId // ""')
   PROFID=$(echo "$BP" | jq -r '.properties.billingProfileId // ""')
   PROFD=$(echo "$BP" | jq -r '.properties.billingProfileDisplayName // "-"')
   PROFNM=$(printf '%s' "$PROFID" | sed -n 's#.*/billingProfiles/\([^/]*\).*#\1#p'); [ -z "$PROFNM" ] && PROFNM="-"

   REL=""; IND=""
   if [ "$AG" = "MicrosoftCustomerAgreement" ] && [ -n "$PROFID" ]; then
    # portable per-profile cache key (no md5sum: missing on stock macOS)
    CK="$D/pc/$(printf '%s' "$PROFID" | tr -c 'A-Za-z0-9' '_')"
    [ -f "$CK" ] || az rest --method get --url "https://management.azure.com${PROFID}?api-version=2024-04-01" 2>/dev/null \
      | jq -r '[(.properties.billingRelationshipType // ""),(.properties.indirectRelationshipInfo.displayName // "")]|@tsv' > "$CK" 2>/dev/null
    [ -s "$CK" ] && IFS=$'\t' read -r REL IND < "$CK"
   fi

   case "$AG" in
    EnterpriseAgreement)            TYPE="EA"; VR="OK";;
    MicrosoftPartnerAgreement)      TYPE="CSP"; VR="NO";;
    MicrosoftCustomerAgreement)
      if [ -n "$IND" ] || { [ -n "$REL" ] && [ "$REL" != "Direct" ]; }; then TYPE="Reseller"; VR="CHK";
      elif [ "$REL" = "Direct" ]; then TYPE="MCA"; VR="OK";
      else TYPE="MCA"; VR="CHK"; fi;;   # REL unconfirmed: could be indirect/reseller -> review, not auto-pass
    MicrosoftOnlineServicesProgram) TYPE="PAYG"; VR="CHK";;
    "")                             TYPE="?"; VR="CHK";;
    *)                              TYPE="$AG"; VR="CHK";;
   esac
   [ "$AG" = "MicrosoftCustomerAgreement" ] && [ "$TYPE" = "MCA" ] && [ -z "$REL" ] && IND="(direct/reseller unconfirmed)"

   ACCTSEG=$(printf '%s' "$BAID" | sed -n 's#.*/billingAccounts/\([^/]*\).*#\1#p')
   VIS="vis"; [ -n "$ACCTSEG" ] && ! grep -qxF "$ACCTSEG" "$D/visible" 2>/dev/null && VIS="hidden"

   if [ "$SKIP_COST" = 0 ]; then
    Q=$(az rest --method post \
     --url "https://management.azure.com/subscriptions/$SUBID/providers/Microsoft.CostManagement/query?api-version=2025-03-01" \
     --headers "Content-Type=application/json" --body "$BODY" 2>/dev/null)
    # empty COST = "couldn't read cost" (no access / API error), distinct from a real 0.00
    if [ $? -ne 0 ] || [ -z "$Q" ] || printf '%s' "$Q" | jq -e '.error' >/dev/null 2>&1; then
     COST=""; CUR="n/a"
    else
     COST=$(echo "$Q" | jq -r '(.properties.columns//[]) as $c|([ $c|to_entries[]|select(.value.name=="Cost" or .value.name=="PreTaxCost")|.key ]|first // 0) as $i|(.properties.rows[0][$i] // 0)' 2>/dev/null)
     CUR=$(echo "$Q" | jq -r '(.properties.columns//[]) as $c|([ $c|to_entries[]|select(.value.name=="Currency")|.key ]|first) as $i|(if $i!=null then (.properties.rows[0][$i] // "-") else "-" end)' 2>/dev/null)
     [ -z "$COST" ] && { COST=""; CUR="n/a"; }
    fi
   else COST=""; CUR="-"; fi

   printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" \
    "$BA" "$BAID" "$TYPE" "$VR" "$PROFD" "$PROFNM" "$SUBNM" "$COST" "$CUR" "$IND" "$VIS" >> "$D/rows"
  done

  read -r OKA CHKA NOA <<<"$(awk -F'\t' '!s[$2]++{if($4=="OK")o++;else if($4=="NO")n++;else c++}END{print o+0,c+0,n+0}' "$D/rows")"
  if [ "$OKA" -gt 0 ] && [ "$NOA" = 0 ] && [ "$CHKA" = 0 ]; then H="PASS — onboardable";
  elif [ "$OKA" -gt 0 ]; then H="PARTIAL — some accounts onboardable, review the rest";
  elif [ "$NOA" -gt 0 ] && [ "$OKA" = 0 ] && [ "$CHKA" = 0 ]; then H="FAIL — CSP/partner-billed, not supported";
  else H="REVIEW — couldn't confirm billing control"; fi
  echo; echo "VERDICT: $H"
  echo " by billing account: OK $OKA onboardable   CHK $CHKA review   CSP/other $NOA unsupported"
  echo

  LC_ALL=C sort -t$'\t' -k2,2 -k6,6 -k8,8rn "$D/rows" | awk -F'\t' '
  function cf(x, s,n,ip,d,sg,r,i,c){ s=sprintf("%.2f",x+0); n=index(s,"."); ip=substr(s,1,n-1); d=substr(s,n);
   if(substr(ip,1,1)=="-"){sg="-";ip=substr(ip,2)} r="";c=0; for(i=length(ip);i>=1;i--){r=substr(ip,i,1) r;c++; if(c%3==0&&i>1)r="," r} return sg r d }
  function spend(seen,v,cur){ return (seen?cf(v):"n/a") (cur!=""?" " cur:"") }
  BEGIN{ S="\001"; ca=S; cp=S }
  {
   if($2!=ca){
    if(ca!=S){ printf "  profile spend: %s\n",spend(pseen,ps,pcur)
               printf " ACCOUNT SPEND: %s\n\n",spend(aseen,as,acur) }
    v=($4=="OK")?"✓ onboard":(($4=="NO")?"✗ not supported":"⚠ review")
    f=""; if($11=="hidden") f=f" (billing acct not visible to you)"
    if($3=="Reseller" && $10!="") f=f" via "$10
    if($3=="MCA" && $10!="") f=f" "$10
    idf=$2; sub(/^.*\/billingAccounts\//,"",idf); if(idf=="") idf="(no access)"
    printf "BILLING ACCOUNT: %s [%s] %s%s\n   id: %s\n",$1,$3,v,f,idf
    ca=$2; as=0; acur=""; aseen=0; cp=S
   }
   if($6!=cp){ if(cp!=S) printf "  profile spend: %s\n",spend(pseen,ps,pcur)
    printf " profile: %s\n",($6=="-"?$5:$6); cp=$6; ps=0; pcur=""; pseen=0 }
   printf "  %-40s %14s %s\n",substr($7,1,40),($8==""?"n/a":cf($8)),($8==""?"":($9=="-"?"":$9))
   if($8!=""){ ps+=$8; as+=$8; pseen=1; aseen=1;
    if($9!="-"&&$9!="n/a"){ if(pcur!=""&&pcur!=$9)pcur="MIXED"; else if(pcur!="MIXED")pcur=$9;
     if(acur!=""&&acur!=$9)acur="MIXED"; else if(acur!="MIXED")acur=$9 } }
  }
  END{ if(ca!=S){ printf "  profile spend: %s\n",spend(pseen,ps,pcur); printf " ACCOUNT SPEND: %s\n",spend(aseen,as,acur) } }'

  if [ -n "$CSV" ]; then
   { echo "billing_account,billing_account_id,type,verdict,profile,profile_id,subscription,cost,currency,partner_or_note,visibility"
    LC_ALL=C sort "$D/rows" | awk -F'\t' 'BEGIN{OFS=","}{for(i=1;i<=NF;i++){gsub(/"/,"\"\"",$i); if($i~/[",]/)$i="\""$i"\""} print}'
   } > "$CSV"; echo; echo "CSV written to $CSV"
  fi
  ```
</Accordion>

**Reading the verdict**

| Verdict     | What it means                                                                                                                         |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| **PASS**    | Every billing account is EA or direct MCA. You are good to proceed.                                                                   |
| **PARTIAL** | Some accounts are onboardable; others need a closer look (reseller-billed MCA, or billing you cannot read).                           |
| **FAIL**    | Only CSP / partner-billed accounts were found. Not supported.                                                                         |
| **REVIEW**  | Could not confirm billing control (often missing billing access). Re-run as a billing admin, or send the output to your nOps contact. |

Each billing account is also flagged **✓ onboard**, **✗ not supported**, or **⚠ review** in the output.

If you are working with an nOps contact, you can share the output (or the CSV written by `--csv results.csv`) so they can confirm your billing scope. The easiest way is to copy everything the script printed, top to bottom, and paste it into your reply.

Prefer not to run a script? You can check in the Azure Portal instead (below).

### How to Check for CSP Subscriptions

The easiest way to verify you don't have CSP subscriptions is through the Azure Marketplace eligibility checker:

<Steps>
  <Step title="Go to Private Offer Management">
    Navigate to [**Azure Portal → Marketplace → Private Offers Center**](https://portal.azure.com/#view/Microsoft_Azure_Marketplace/PrivateOffersCenter.ReactView)
  </Step>

  <Step title="Click Check Eligibility">
    Click the **Check Eligibility** button on the right-hand side
  </Step>

  <Step title="Review the Results">
    Look for the eligibility check results. You should see:

    * ✅ **Green checkmark**: "No Cloud Solution Provider (CSP) subscription found" — **You can use nOps**
    * ❌ **Red X**: "Cloud Solution Provider (CSP) subscription found" — **You cannot use nOps**
  </Step>
</Steps>

<Info>
  If you find that you have CSP subscriptions, please contact your CSP reseller/partner for cost optimization solutions. You may need to migrate to a direct EA or MCA agreement with Microsoft if you want to use nOps in the future.
</Info>

***

## 3. Finding Your Management Group ID

nOps assigns RBAC roles at the management group scope to provide visibility across all subscriptions in that group.

1. Go to [**Management Groups**](https://portal.azure.com/#view/Microsoft_Azure_ManagementGroups/ManagementGroupBrowseBlade) in the Azure Portal.
2. If you want nOps to see **all subscriptions**, use the **Tenant Root Group** -- its ID matches your **Tenant ID**.
3. If you want to limit nOps to a subset of subscriptions, find the appropriate management group and note its **ID**.

<Tip>
  If your organization does not use management groups, you can use your **Tenant ID** as the Management Group ID. This is equivalent to the root management group and covers all subscriptions.
</Tip>

***

## 4. Preparing a Subscription for FOCUS Exports

You will need an Azure subscription where nOps can create resources for cost data exports. Choose the subscription with the most spend.

<Info>
  The setup will create a resource group, storage account, and FOCUS export in this subscription. These resources incur minimal cost (storage fees only).
</Info>

### Required Resource Providers

#### For FOCUS Exports (on the export subscription)

These must be registered on the subscription where the storage account will be created:

| Resource Provider                 | Purpose                               |
| --------------------------------- | ------------------------------------- |
| `Microsoft.Storage`               | Storage account for FOCUS export data |
| `Microsoft.CostManagement`        | Cost queries and management           |
| `Microsoft.CostManagementExports` | FOCUS export creation and scheduling  |

<Tabs>
  <Tab title="Portal">
    1. Go to [**Subscriptions**](https://portal.azure.com/#view/Microsoft_Azure_Billing/SubscriptionsBlade) > select your export subscription > **Settings** > **Resource providers**
    2. Search for each provider above and click **Register**
    3. Wait for all three to show **Registered** (1-2 minutes)
  </Tab>

  <Tab title="CLI">
    Run in [Azure Cloud Shell](https://shell.azure.com):

    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    az provider register --namespace Microsoft.Storage --subscription "<subscription-id>" && \
    az provider register --namespace Microsoft.CostManagement --subscription "<subscription-id>" && \
    az provider register --namespace Microsoft.CostManagementExports --subscription "<subscription-id>"
    ```
  </Tab>
</Tabs>

<Info>
  Most subscriptions already have `Microsoft.Storage` and `Microsoft.CostManagement` registered. `Microsoft.CostManagementExports` is the one most likely to require manual registration, especially on newer subscriptions. The Terraform setup method handles this automatically.
</Info>

#### For Lighthouse (on the deployment subscription)

Lighthouse requires `Microsoft.ManagedServices` to be registered on the subscription where you deploy:

| Resource Provider           | Purpose                              |
| --------------------------- | ------------------------------------ |
| `Microsoft.ManagedServices` | Lighthouse delegated resource access |

<Tabs>
  <Tab title="Portal">
    1. Go to [**Subscriptions**](https://portal.azure.com/#view/Microsoft_Azure_Billing/SubscriptionsBlade) > select the subscription > **Settings** > **Resource providers**
    2. Search for `Microsoft.ManagedServices`, click **Register**
  </Tab>

  <Tab title="CLI">
    Run in [Azure Cloud Shell](https://shell.azure.com):

    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    az provider register --namespace Microsoft.ManagedServices --subscription "<subscription-id>"
    ```
  </Tab>
</Tabs>

<Info>
  Most subscriptions already have this registered. If it already shows "Registered", no action is needed.
</Info>

### Storage Account Network Access

The FOCUS export storage account must allow public network access so that the nOps data ingestion service can read the exported cost data. If your organization enforces storage account firewalls via Azure Policy, you may need to whitelist the nOps IP addresses (`44.246.45.84`, `52.11.53.164`, and `52.39.155.200`) on the storage account after creation. The Terraform setup method detects this automatically and prompts you to add the IPs.

***

## 5. Understanding RBAC Roles for nOps

nOps requires specific Azure RBAC roles to operate. These roles are assigned at different scopes depending on the integration tier.

### Savings Analysis Roles (Read-Only)

| Scope            | Role                        | Role Definition ID                     | Purpose                                    |
| ---------------- | --------------------------- | -------------------------------------- | ------------------------------------------ |
| Management Group | Reader                      | `acdd72a7-3385-48ef-bd42-f606fba81ae7` | View all resources across subscriptions    |
| Management Group | Cost Management Contributor | `434105ed-43f6-45c7-a02f-909b2ba83430` | Query cost data, create and manage exports |
| Management Group | Monitoring Reader           | `43d0d8ad-25c7-4714-9337-8ba259a9fe05` | View monitoring metrics and diagnostics    |
| Storage Account  | Owner                       | `8e3af657-a8ff-443c-a75c-2fe8c4bcb635` | Access FOCUS export data                   |
| Storage Account  | Storage Blob Data Reader    | (built-in)                             | Read blob data from storage account        |

<Info>
  The **Owner** role is granted exclusively on the storage account created by nOps for FOCUS exports (`nopsfocus<tenant-prefix>`) -- it does not grant Owner access to any other resources in your environment.
</Info>

\| Tenant (Reservations) | Reservations Reader | `582fc458-8989-419f-a480-75249bc5db7e` | Read all reservations in the tenant |
\| Tenant (Savings Plans) | Savings Plan Reader | `d534ad90-4ac5-4815-a178-b2e47397baab` | Read all savings plans in the tenant |

### Billing Access Roles

| Billing Account Type               | Role Assigned          | Scope                        |
| ---------------------------------- | ---------------------- | ---------------------------- |
| Enterprise Agreement (EA)          | EnrollmentReader       | Billing Account (Enrollment) |
| Microsoft Customer Agreement (MCA) | Billing Account Reader | Billing Account              |

### Commitment Management Roles (Additional)

These roles are added on top of Savings Analysis roles when you upgrade to Commitment Management:

| Scope                                           | Role                        | Role Definition ID                     | Purpose                                               |
| ----------------------------------------------- | --------------------------- | -------------------------------------- | ----------------------------------------------------- |
| Tenant (`/providers/Microsoft.Capacity`)        | Reservations Contributor    | `b4ebc951-a0c2-41f7-a3cd-a57fe27c8e3a` | Read and manage reservations (programmatic + Portal)  |
| Tenant (`/providers/Microsoft.BillingBenefits`) | Savings Plan Contributor    | `28c0d4cd-558d-4de9-91a0-faa18e7b3266` | Read and manage savings plans (programmatic + Portal) |
| Dedicated Subscription (via Lighthouse)         | Reservations Purchaser      | `f7b75c60-3036-4b75-91c3-6b41c27c1689` | Purchase reservations on the dedicated subscription   |
| Dedicated Subscription (via Lighthouse)         | Savings Plan Purchaser      | `3d24a3a0-c154-4f6f-a5ed-adc8e01ddb74` | Purchase savings plans on the dedicated subscription  |
| Billing Account (EA)                            | EA Purchaser                | `da6647fb-7651-49ee-be91-c43c4877f0c4` | Purchase via EA billing                               |
| Billing Profile (MCA)                           | Billing Profile Contributor | (built-in)                             | Purchase via MCA billing                              |

***

## 6. Purchase Policies (Commitment Management Only)

If you plan to use **Commitment Management** for reservation and savings plan purchases, your billing account must have purchase policies enabled. Without these, only billing administrators can make purchases — users with the Reservations Purchaser RBAC role (including those delegated via Azure Lighthouse) will be blocked.

| Billing Account Type               | Policy Location                  | Setting                                         |
| ---------------------------------- | -------------------------------- | ----------------------------------------------- |
| Enterprise Agreement (EA)          | Enrollment > Settings > Policies | **Reserved Instances** must be **On**           |
| Microsoft Customer Agreement (MCA) | Billing Profile > Policies       | **Azure Reservation purchases** must be **Yes** |
| Microsoft Customer Agreement (MCA) | Billing Profile > Policies       | **Azure Savings Plan** must be **Yes**          |

<Info>
  For MCA, only the billing profile associated with the dedicated subscription needs these policies enabled. You do not need to change policies on other billing profiles.
</Info>

***

## Next Steps

Once you have your Management Group ID, know your billing account type, and have a subscription ready for FOCUS exports, you are ready to connect Azure to nOps.

<Tip>
  **Ready to connect?** Go to [nOps Settings > Cloud Provider Integrations](https://clara.nops.io/settings) and click the **Microsoft Azure** card to start the integration wizard.
</Tip>

nOps offers two onboarding tiers. Start with Savings Analysis, then upgrade to Commitment Management when ready:

<CardGroup cols={2}>
  <Card title="Savings Analysis Setup" icon="plug" href="/azure/integration-setup">
    Read-only cost visibility, resource inventory, optimization recommendations, and daily FOCUS cost exports.
  </Card>

  <Card title="Commitment Management Setup" icon="chart-line" href="/azure/commitment-management-setup">
    Automated purchasing and management of Azure Reservations and Savings Plans. Requires Savings Analysis first.
  </Card>
</CardGroup>
