Integration
Grant Suger the necessary access to manage your Azure Marketplace on hour behalf, no more no less.
Overview
Selling on Azure Marketplace is managed through Microsoft Partner Center. Your business is required to enroll and get a verifed Microsoft Partner Network (MPN) ID. More details can be found here.
Prerequisite
Once your Microsoft Partner Network (MPN) is verified, follow the steps below to prepare the resources required for the Suger integration.
Connect Azure AD Tenant
Associate one Azure AD Tenant with your Microsoft Partner Center tenants.
Register Azure AD Application
Choose one of the two paths below. Both end up with the same Azure AD Application configured with the right permissions and a client secret.
| Path | Best for | Time |
|---|---|---|
| A. Quick Setup (Script) β recommended | A fresh setup with no existing Azure Marketplace integration | ~2 minutes |
| B. Manual Setup | Customers who cannot run shell scripts, or who want to reuse an existing Azure AD Application | ~10 minutes |
Path A β Quick Setup (Script)
The script below creates the Azure AD Application, configures the redirect URI + implicit grant, adds the required API permissions, grants admin consent, generates a 1-year (365-day) client secret, and self-verifies the end-to-end token flow β all in a single run.
Prerequisites for the script:
- Azure CLI (
az),jq, andcurlβ the easiest way to satisfy all three is to use Azure Cloud Shell (everything is pre-installed). If you prefer to run locally, the script will print OS-specific install commands if a tool is missing. - Signed in to the correct Azure tenant β if not, the script will run
az loginfor you (Cloud Shell is already signed in). - You are a Global Administrator in the target tenant (see the callout above).
Get the script: Download setup-azure-app.sh β or view the source in the collapsible block below.
Run the script:
chmod +x setup-azure-app.sh
./setup-azure-app.sh
Rotate the client secret β before the 365-day expiry, or any time you want to invalidate the existing one:
REUSE_EXISTING=true ./setup-azure-app.sh
The script appends a new secret without invalidating the old one, so there is no downtime. Paste the new value into the Suger Console, then (optionally) remove the old secret from the Azure Portal after youβve confirmed Suger is using the new one.
When the script finishes, it prints three values to the terminal and also writes them to ./suger-azure-credentials.txt (mode 600):
TENANT_IDAPP_ID(this is yourClient ID/Application ID)CLIENT_SECRET
Click to expand: full `setup-azure-app.sh` script
#!/usr/bin/env bash
# Suger Azure Marketplace β Azure-side one-shot setup script.
#
# In the customer's Azure tenant, this script configures the Entra app + permissions + secret in one run:
# 1) Create (or reuse) a multitenant Entra application with Suger's Web redirect URI + implicit grant enabled
# 2) Pre-create service principals for the Marketplace SaaS API + 3 Partner APIs
# 3) Grant the app `user_impersonation` on the 3 Partner APIs (needed for co-sell)
# (Marketplace SaaS API does not require a declarative grant β the token endpoint reads `resource=`)
# 4) Grant admin consent
# 5) Generate a client secret (default 365 days)
# 6) Self-verify by exchanging the secret for a real Marketplace API token
# At the end, the Tenant ID / App ID / Client Secret are printed and written to ./suger-azure-credentials.txt (chmod 600).
#
# Prerequisites (auto-detected β non-technical users do not need to install anything in advance):
# - Azure CLI (az) + jq + curl: if missing, the script prints OS-specific install commands
# (Azure Cloud Shell is the simplest option β all three are pre-installed)
# - Azure login: if not signed in, the script will run `az login` (opens a browser)
# - The executor must be a Global Administrator in the target tenant (required by Suger)
#
# Usage:
# # First-time setup
# ./setup-azure-app.sh
#
# # Rotate the client secret (run before the 365-day expiry; appends a new secret,
# # the old one stays valid until its natural expiry β no downtime)
# REUSE_EXISTING=true ./setup-azure-app.sh
#
# # ββ Advanced options below; 99% of customers do not need these ββ
# SECRET_DAYS=90 ./setup-azure-app.sh # Customize secret lifetime (default 365 days)
# APP_NAME="Suger Custom Name" ./setup-azure-app.sh # Customize the Entra app display name
# SKIP_VERIFY=true ./setup-azure-app.sh # Skip token self-verification (offline/restricted networks)
# CLEANUP=true ./setup-azure-app.sh # β οΈ Dangerous: deletes same-named apps and recreates. Changes the App ID, breaking any existing offer Technical Configuration. Testing only
# Note: -u has quirks with heredoc / command substitution on macOS bash 5.2,
# so it's intentionally not enabled. We keep -e (exit on error) and pipefail.
set -eo pipefail
APP_NAME="${APP_NAME:-Suger Marketplace Integration}"
SECRET_DAYS="${SECRET_DAYS:-365}"
REUSE_EXISTING="${REUSE_EXISTING:-false}"
CLEANUP="${CLEANUP:-false}"
SKIP_VERIFY="${SKIP_VERIFY:-false}"
OUTPUT_FILE="${OUTPUT_FILE:-./suger-azure-credentials.txt}"
# Well-known Microsoft resource IDs in the public directory
MARKETPLACE_SAAS_RESOURCE_ID="20e940b3-4c77-4b0b-9a53-9e16a1b010a7" # Marketplace SaaS Fulfillment API (client_credentials)
MICROSOFT_PARTNER_RESOURCE_ID="4990cffe-04e8-4e8b-808a-1175604b879f" # MicrosoftPartner (legacy partner API)
PARTNER_CENTER_RESOURCE_ID_1="fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd" # Microsoft Partner Center #1
PARTNER_CENTER_RESOURCE_ID_2="fabfbdc4-5751-471c-ac43-3826fa1afc31" # Microsoft Partner Center #2
SUGER_REDIRECT_URI="https://api.suger.cloud/public/integration/azure/authCode"
# 3 Partner APIs that need user_impersonation
PARTNER_RESOURCE_IDS=(
"$MICROSOFT_PARTNER_RESOURCE_ID"
"$PARTNER_CENTER_RESOURCE_ID_1"
"$PARTNER_CENTER_RESOURCE_ID_2"
)
# All resources that need an SP pre-created (includes Marketplace SaaS)
ALL_RESOURCE_IDS=(
"$MARKETPLACE_SAAS_RESOURCE_ID"
"${PARTNER_RESOURCE_IDS[@]}"
)
# OS detection (used for OS-specific install instructions)
detect_os() {
case "$(uname -s)" in
Darwin*) echo "macos" ;;
Linux*)
if grep -qiE 'ubuntu|debian' /etc/os-release 2>/dev/null; then echo "debian"
elif grep -qiE 'rhel|centos|fedora|rocky|alma' /etc/os-release 2>/dev/null; then echo "rhel"
else echo "linux"; fi
;;
MINGW*|CYGWIN*|MSYS*) echo "windows" ;;
*) echo "unknown" ;;
esac
}
# Friendly prompt when tools are missing: recommend Cloud Shell first, then local install
missing_tools=()
command -v az >/dev/null 2>&1 || missing_tools+=("az")
command -v jq >/dev/null 2>&1 || missing_tools+=("jq")
command -v curl >/dev/null 2>&1 || missing_tools+=("curl")
if (( ${#missing_tools[@]} > 0 )); then
echo "β Missing tools: ${missing_tools[*]}"
echo
echo "π¦ Easiest option: use Azure Cloud Shell (browser-based, no install, auto-signed-in)"
echo " β Open https://shell.azure.com, choose Bash, upload this script and run it"
echo
echo "To install locally:"
case "$(detect_os)" in
macos)
echo " brew install azure-cli jq # curl is built into macOS"
;;
debian)
echo " curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash"
echo " sudo apt-get install -y jq curl"
;;
rhel)
echo " sudo dnf install -y azure-cli jq curl"
;;
windows)
echo " Recommended: install WSL (Windows Subsystem for Linux) first, then follow the debian instructions"
echo " Or install Azure CLI for Windows directly: https://aka.ms/installazurecliwindows"
;;
*)
echo " Azure CLI install docs: https://learn.microsoft.com/cli/azure/install-azure-cli"
;;
esac
exit 1
fi
# Filter stderr noise (WARNING lines) but keep real errors visible.
run_az() {
local out rc tmperr
tmperr=$(mktemp)
if out=$(az "$@" 2>"$tmperr"); then
grep -v "^WARNING" "$tmperr" >&2 || true
rm -f "$tmperr"
printf '%s' "$out"
return 0
else
rc=$?
cat "$tmperr" >&2
rm -f "$tmperr"
return $rc
fi
}
# Wait for an SP to become visible in the directory (up to ~30s).
wait_for_sp() {
local id="$1" label="$2" i
for i in $(seq 1 15); do
if az ad sp show --id "$id" >/dev/null 2>&1; then return 0; fi
sleep 2
done
echo "β Timed out waiting for $label service principal (id=$id)"
return 1
}
echo "==> Current Azure context"
if ! az account show >/dev/null 2>&1; then
echo "β οΈ Not signed in to Azure β invoking az login..."
echo " (Opens a browser. For headless/remote shells, run manually:"
echo " az login --use-device-code --tenant <your-tenant-id>)"
if ! az login; then
echo "β Login failed or cancelled"
exit 1
fi
fi
az account show --query "{tenantId: tenantId, subscription: name, user: user.name}" -o table
TENANT_ID=$(az account show --query tenantId -o tsv)
read -r -p "Create/update the Suger integration app in this tenant? [y/N] " ans
[[ "$ans" =~ ^[Yy]$ ]] || { echo "Cancelled"; exit 1; }
echo
echo "==> 1/6 Register Entra application (multitenant + Suger redirect URI + implicit grant): $APP_NAME"
# CLEANUP=true: delete all same-named apps (including historical failures), then go through the fresh-create path
if [[ "$CLEANUP" =~ ^([Tt]rue|1)$ ]]; then
EXISTING_IDS="$(az ad app list --display-name "$APP_NAME" --query "[].appId" -o tsv 2>/dev/null || true)"
if [[ -n "$EXISTING_IDS" ]]; then
echo " CLEANUP=true β deleting same-named apps:"
for id in $EXISTING_IDS; do
run_az ad app delete --id "$id"
echo " deleted $id"
done
else
echo " CLEANUP=true β no same-named apps to delete"
fi
fi
EXISTING_APP_ID="$(az ad app list --display-name "$APP_NAME" --query "[0].appId" -o tsv 2>/dev/null || true)"
EXISTING_APP_ID="${EXISTING_APP_ID:-}"
if [[ -n "$EXISTING_APP_ID" ]]; then
if [[ "${REUSE_EXISTING:-false}" =~ ^([Tt]rue|1)$ ]]; then
APP_ID="$EXISTING_APP_ID"
echo " Exists β reusing APP_ID=$APP_ID"
# Align existing app settings with the official requirements
run_az ad app update --id "$APP_ID" \
--sign-in-audience AzureADMultipleOrgs \
--web-redirect-uris "$SUGER_REDIRECT_URI" \
--enable-id-token-issuance true \
--enable-access-token-issuance true >/dev/null
echo " Aligned multitenant + redirect URI + implicit grant"
else
echo "β An app with the same name already exists (appId=${EXISTING_APP_ID})."
echo " To re-run, set REUSE_EXISTING=true (keeps the app, re-applies permissions/consent + appends a new secret);"
echo " or change APP_NAME to use a different name."
exit 1
fi
else
APP_ID=$(run_az ad app create \
--display-name "$APP_NAME" \
--sign-in-audience AzureADMultipleOrgs \
--web-redirect-uris "$SUGER_REDIRECT_URI" \
--enable-id-token-issuance true \
--enable-access-token-issuance true \
--is-fallback-public-client false \
--query appId -o tsv)
run_az ad sp create --id "$APP_ID" >/dev/null
echo " Created APP_ID=$APP_ID"
fi
wait_for_sp "$APP_ID" "this app"
echo
echo "==> 2/6 Pre-create service principals for 4 Microsoft APIs"
for RESOURCE_ID in "${ALL_RESOURCE_IDS[@]}"; do
if az ad sp show --id "$RESOURCE_ID" >/dev/null 2>&1; then
echo " $RESOURCE_ID already exists"
else
run_az ad sp create --id "$RESOURCE_ID" >/dev/null
echo " $RESOURCE_ID created"
fi
wait_for_sp "$RESOURCE_ID" "$RESOURCE_ID"
done
echo
echo "==> 3/6 Add API permissions"
# Microsoft Graph User.Read: the portal adds this by default when creating an app via the UI,
# but the CLI does not. Add it explicitly to match the portal-created standard.
GRAPH_RESOURCE_ID="00000003-0000-0000-c000-000000000000"
GRAPH_USER_READ_SCOPE_ID="e1fe6dd8-ba31-4d61-89e7-88639da4683d"
run_az ad app permission add \
--id "$APP_ID" \
--api "$GRAPH_RESOURCE_ID" \
--api-permissions "${GRAPH_USER_READ_SCOPE_ID}=Scope" >/dev/null
echo " Microsoft Graph User.Read β"
# Marketplace SaaS: the SP is in the tenant; client_credentials flow does not require a declarative grant β skip
echo " Marketplace SaaS: only requires SP in tenant; token endpoint reads resource=, no declarative grant needed"
# 3 Partner APIs: add user_impersonation (delegated, required for co-sell flows)
for RESOURCE_ID in "${PARTNER_RESOURCE_IDS[@]}"; do
SCOPE_ID=$(az ad sp show --id "$RESOURCE_ID" \
--query "oauth2PermissionScopes[?value=='user_impersonation'].id | [0]" -o tsv 2>/dev/null || true)
if [[ -n "$SCOPE_ID" ]]; then
run_az ad app permission add \
--id "$APP_ID" \
--api "$RESOURCE_ID" \
--api-permissions "${SCOPE_ID}=Scope" >/dev/null
echo " $RESOURCE_ID user_impersonation β"
else
echo " β οΈ $RESOURCE_ID does not expose the user_impersonation scope, skipping"
fi
done
echo
echo " β οΈ For co-sell, you also need to assign the Referrals admin role in the Partner Center Referrals workspace β see the docs"
echo
echo "==> 4/6 Grant admin consent + explicit grant (fallback)"
# First run admin-consent (covers the whole app in one call)
consent_ok=false
for i in $(seq 1 10); do
if run_az ad app permission admin-consent --id "$APP_ID" >/dev/null 2>&1; then
consent_ok=true
break
fi
sleep 3
done
if ! $consent_ok; then
echo "β admin-consent failed. Common causes:"
echo " - The current account is not a Global Administrator (Suger requires Global Admin)"
echo " - The tenant has disabled user/app consent"
exit 1
fi
# Known az CLI quirk: admin-consent returning 0 doesn't always mean OAuth2PermissionGrant was created.
# Apply explicit `permission grant` as a fallback (idempotent β will not duplicate).
sleep 3 # let admin-consent propagate
for RESOURCE_ID in "$GRAPH_RESOURCE_ID" "${PARTNER_RESOURCE_IDS[@]}"; do
SCOPE_VALUE="user_impersonation"
[[ "$RESOURCE_ID" == "$GRAPH_RESOURCE_ID" ]] && SCOPE_VALUE="User.Read"
run_az ad app permission grant \
--id "$APP_ID" \
--api "$RESOURCE_ID" \
--scope "$SCOPE_VALUE" >/dev/null 2>&1 || true
done
echo " β admin-consent + explicit grant complete"
echo
echo "==> 5/6 Generate client secret (${SECRET_DAYS}-day validity)"
# Use --end-date instead of --years for precise day-level expiry. Default 365 days
# (~1 year) matches a standard secret-rotation cadence; rotate by re-running
# with REUSE_EXISTING=true before expiry.
SECRET_END_DATE=$(
date -u -d "+${SECRET_DAYS} days" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|| date -u -v+"${SECRET_DAYS}"d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null
)
[[ -n "$SECRET_END_DATE" ]] || { echo "β Could not compute secret expiry"; exit 1; }
SECRET=$(run_az ad app credential reset \
--id "$APP_ID" \
--end-date "$SECRET_END_DATE" \
--append \
--display-name "suger-$(date +%Y%m%d)" \
--query password -o tsv)
[[ -n "$SECRET" ]] || { echo "β Failed to generate client secret"; exit 1; }
SECRET_EXPIRY_DATE="${SECRET_END_DATE%%T*}"
echo
echo "==> 6/6 Self-verify: exchange the new secret for a Marketplace API token"
if [[ "$SKIP_VERIFY" =~ ^([Tt]rue|1)$ ]]; then
echo " SKIP_VERIFY=true, skipping"
else
# Freshly-issued secrets occasionally take a moment to propagate in AAD β retry up to ~30s.
verify_ok=false
for i in $(seq 1 10); do
resp=$(curl -sS -X POST \
"https://login.microsoftonline.com/${TENANT_ID}/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=${APP_ID}" \
--data-urlencode "client_secret=${SECRET}" \
--data-urlencode "resource=${MARKETPLACE_SAAS_RESOURCE_ID}" || true)
if echo "$resp" | jq -e '.access_token' >/dev/null 2>&1; then
verify_ok=true
break
fi
sleep 3
done
if $verify_ok; then
echo " β Got a Marketplace API token β the end-to-end auth flow is working"
else
echo " β οΈ Self-verification failed. Suger will likely also fail to authenticate. Last response:"
echo "$resp" | jq . 2>/dev/null || echo "$resp"
echo " Continuing to print credentials; resolve the issue before handing them over to Suger."
fi
fi
# Persist credentials to a file to avoid scrollback/copy loss.
umask 077
cat > "$OUTPUT_FILE" <<EOF
# Suger Azure integration credentials (generated $(date -u +%Y-%m-%dT%H:%M:%SZ))
# β οΈ Contains a client secret β store safely and delete this file afterwards.
APP_NAME="$APP_NAME"
TENANT_ID="$TENANT_ID"
APP_ID="$APP_ID"
CLIENT_SECRET="$SECRET"
SECRET_EXPIRES_IN_DAYS=$SECRET_DAYS
SECRET_EXPIRY_DATE="$SECRET_EXPIRY_DATE"
EOF
chmod 600 "$OUTPUT_FILE"
cat <<EOF
================================================================
β
Azure-side setup complete. Paste these three values into the Suger Console:
================================================================
Tenant ID: $TENANT_ID
Application ID: $APP_ID
Client Secret: $SECRET
Secret expires: $SECRET_EXPIRY_DATE β οΈ Rotate before expiry or Suger sync will silently break
Look up your Publisher ID in Partner Center β Account settings,
and fill it in alongside the three values above (the fourth field in the Suger UI).
π Persisted (mode 600) to: $OUTPUT_FILE
================================================================
Next step: configure Technical Configuration for your SaaS offer in Partner Center
================================================================
Microsoft Entra tenant ID: $TENANT_ID
Microsoft Entra application ID: $APP_ID
Landing page URL: <provided by Suger, format:
https://api.{region}.suger.io/public/signup/azure/orgId/{orgId}>
Connection webhook: <provided by Suger, format:
https://api.{region}.suger.io/public/azure/fulfillment/webhook/orgId/{orgId}>
Save β republish the offer β the next Marketplace order will deliver a webhook to Suger.
β οΈ The App ID in Technical Configuration MUST equal the APP_ID above, or both directions break:
- Suger rejects incoming MS webhooks on JWT aud mismatch
- MS SaaS Fulfillment API rejects Suger's calls: "token is valid, however it does not
include the correct audience, the entra tenant id, or the entra app id"
================================================================
β οΈ The client secret was written to $OUTPUT_FILE. Copy it to a password manager, then delete that file.
================================================================
EOF
Once the script completes, skip ahead to Add Azure AD Application to Partner Center.
Path B β Manual Setup
Use this path if you cannot run shell scripts, or if you want to reuse an existing Azure AD Application (for example, when migrating from a self-built fulfillment system).
Register an Azure AD Application in the previous Azure AD Tenant, ensuring that:
- Add Web Redirect URL in the
Application. The Redirect URL should behttps://api.suger.cloud/public/integration/azure/authCode.
- Under
Implicit grant and hybrid flows, check both:
- Access tokens (used for implicit flows)
- ID tokens (used for implicit and hybrid flows)
- Add the required API permissions. Go to API permissions β + Add a permission β APIs my organization uses, and search for
Microsoft Partner. The following APIs should appear:
MicrosoftPartner(App ID:4990cffe-04e8-4e8b-808a-1175604b879f)Microsoft Partner Center(App ID:fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd)Microsoft Partner Center(App ID:fabfbdc4-5751-471c-ac43-3826fa1afc31)
For each of the three APIs, click on the API name, select the user_impersonation permission, and click Add permissions.
-
Click Grant admin consent for {your organization name} to finalize the permissions. If the button is greyed out, your account is not a Global Administrator.
-
Generate a client secret under Certificates & secrets β + New client secret. Set the expiry to 1 year (365 days) (recommended β matches a standard rotation cadence) and copy the Value immediately; you cannot view it again after leaving the page.
Add Azure AD Application
This step is required regardless of which path (A or B) you used above.
Add the Azure AD Application created in the previous step to your Microsoft Partner Center User Management with all five roles (Manager, Developer, Business Contributor, Finance Contributor, Marketer) assigned.
Enroll Partner Programs
There are two programs Commercial Marketplace and Microsoft AI Cloud Partner Program to enroll before we can start the marketplace listing and offers, as shown below in Microsoft Partner Center.
Create Integration
Now all required resources are ready from upper steps, then click the button CONNECT and fill the dialog of Azure integration on Suger Console with info below.
- Tenant ID: The ID of the
Azure AD Tenantassociated with yourMicrosoft Partner Center. - Client ID: It is also called
Application ID, It is the ID of theAzure AD Applicationcreated & added to yourMicrosoft Partner Center. - Client Secret: Also called
Application Secret,Secret Key, orApplication Key. If you used Path A, the script already printed it and saved it tosuger-azure-credentials.txt. If you used Path B, you generated it manually in the Azure Portal β see the Microsoft reference. - User ID (optional): The email address to log in to
Microsoft AzureandMicrosoft Partner Center. - User Secret (optional): The password to log in to
Microsoft AzureandMicrosoft Partner Center.
You can find the Tenant ID and Client ID (Application ID) on the Azure AD Application overview page:
Once clicked the button CREATE, you are redirected to Azure auth portal to grant OAuth code for the Application & Suger service.
Complete Integration β Product Technical Configuration
When listing your product in Microsoft Partner Center, verify the following in the Technical Configuration section:
- The Azure AD Tenant ID is the same one used in the Suger integration.
- The Azure AD Application (Client) ID is the same one used in the Suger integration.
- The Landing Page URL is set to the Suger-provided endpoint.
- The Connection Webhook is set to the Suger-provided endpoint.
Edit Integration
You can edit the integration to update the following fields:
- Client Secret: The client secret expires (365 days / 1 year by default if generated via Path A). Before it expires, generate a fresh secret and paste the new value here, otherwise sync will silently stop working.
- Enable Entitlement End Soon Notification: When enabled, specify the number of days (10-60) before an entitlement ends to trigger notifications. Suger will send an initial notification when Azure Marketplace entitlements approach ending, followed by reminders every 5 days. To configure recipients, follow the email notification configuration guide and add the scope
END_SOON.ENTITLEMENT.
Delete Integration
The Azure integration can be deleted like all other integrations. Once the deletion is triggered, all integration info including the Application Key will be deleted immediately & permanently from Suger. No time window or methods to recover.



