> For the complete documentation index, see [llms.txt](https://docs.heeler.com/mrecEO40m5D6bt7Pq5pE/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.heeler.com/mrecEO40m5D6bt7Pq5pE/get-started/source-code-scm/azure-devops-app-registration.md).

# Azure DevOps (App registration / federated)

Connect Azure DevOps to Heeler with an app-only Microsoft Entra Service Principal and workload identity federation — no human user, no license dependency, and no secret stored by Heeler.

Connect your Azure DevOps organization to Heeler as an **application** — a Microsoft Entra **Service Principal** in your own tenant — instead of on behalf of a person. Heeler authenticates using **workload identity federation**: it presents a short-lived, Heeler-signed token that your app registration trusts, so **no client secret is ever created or stored by Heeler**.

{% hint style="success" %}
**This is the recommended way to connect Azure DevOps.** The identity is an application rather than a person, so the connection survives offboarding, licence changes and permission changes — and Heeler never stores a secret. A [Personal Access Token](/mrecEO40m5D6bt7Pq5pE/get-started/source-code-scm/azure-devops.md) is still supported; see [how it compares with a PAT](/mrecEO40m5D6bt7Pq5pE/get-started/source-code-scm/azure-devops.md#app-registration-or-a-personal-access-token).
{% endhint %}

## Why app-only

* **No human-user dependency.** The identity is an application, so nothing breaks when a person offboards or loses a license.
* **No third-party OAuth consent.** The Service Principal lives in your tenant; there is no per-user consent to a Heeler-owned OAuth app, so tenants that disable third-party consent can still connect.
* **No stored secret.** Federation replaces a client secret with a signed assertion from Heeler's OIDC issuer that your app registration trusts.

{% hint style="warning" %}
**A Service Principal still consumes a Basic access level in the organization.** This removes the *human-user* dependency, not the license seat. The Service Principal must be added with a **Basic** access level — a **Stakeholder** license cannot read Git repositories, and code analysis will fail.
{% endhint %}

## Before you start

* Azure DevOps Services organization is **backed by Microsoft Entra ID**. Check under **Organization settings → Microsoft Entra** to confirm your organization is connected to the directory (<https://dev.azure.com/{your\\_organization\\_name}/\\_settings/organizationAad>)
* Permission to **register an application** in your Microsoft Entra tenant.
* **Project Collection Administrator** permission in Azure DevOps (to add the Service Principal and grant it the required permissions).
* For the recommended least-privilege service-hooks grant, the **Azure DevOps CLI** (`az` with `az extension add --name azure-devops`) signed in to your organization — or follow the portal alternative shown in that step.
* **Administrator** role in Heeler.
* If your organization uses an **IP allow list**, allow Heeler's outbound IPs on **port 443**: `44.221.229.40`, `52.73.231.96`.

## Set up the connection

{% stepper %}
{% step %}

### Read Heeler's federation values

In **Heeler**, click the **Settings** (gear) icon → **Connections → Code Organizations → Add Code Organization → Azure DevOps (App registration / federated)**. The first step shows three values to copy — **Issuer**, **Subject**, and **Audience**. Keep this open; you'll paste them into Entra.
{% endstep %}

{% step %}

### Register an app in Microsoft Entra

In the **Microsoft Entra admin center**, go to **App registrations → New registration**, give it a name (e.g. `Heeler Azure DevOps`), and register it. Note the **Application (client) ID** and **Directory (tenant) ID** on the app's Overview page — you'll enter both in Heeler when ready.
{% endstep %}

{% step %}

### Add a federated credential

On your app registration, go to **Certificates & secrets → Federated credentials → Add credential** and choose the **Other issuer** scenario. Enter:

* **Issuer** — the Issuer value from Heeler (e.g. `https://app.heeler.com`).
* **Subject identifier** — the Subject value from Heeler (e.g. `heeler-ado-connector`).
* **Name** — add a name for the credential (e.g. `heeler-security`).
* **Audience** — confirm value is `api://AzureADTokenExchange`.

Add.
{% endstep %}

{% step %}

### Add the Service Principal to your organization

Sign in to **Azure DevOps** as a **Project Collection Administrator** and go to **Organization settings → Users**, then select **Add users**. Fill in:

* **Users** — the **display name** of your app registration (from the **Register an app in Microsoft Entra** step, e.g. `Heeler Azure DevOps`).
* **Access level** — **Basic**. A **Stakeholder** license cannot read Git repositories, and code analysis will fail.
* **Add to projects** — select at least one project (this makes the Service Principal a member). Heeler analyzes the whole organization; the next step grants access org-wide, so this choice doesn't limit scope.
* **Azure DevOps Groups** — set this to **Project Readers** (not the **Project Contributors** default) so the Service Principal starts with no write or push access. The next step grants exactly the code-read, pull-request, and webhook permissions Heeler needs — org-wide across all projects.

Select **Add**.
{% endstep %}

{% step %}

### Grant least-privilege permissions

Heeler reads repositories (code + pull requests) and creates **Service Hook** subscriptions (the webhooks in a later step) *as the Service Principal*. Org-wide code read isn't a portal action and the portal doesn't expose service-hook permissions, so the least-privilege way to grant everything across **every project (current and future)** is three organization-root grants via the [Azure DevOps CLI](https://learn.microsoft.com/azure/devops/cli/). Prefer clicking? Use the portal admin-group alternative at the end.

All three grants matter. The **Readers** membership from the previous step only makes the projects selected there *visible* to the Service Principal — Azure DevOps hides every other project (and all of its repositories) from listing, no matter what repository permissions exist. Without the project-visibility grant below, projects added later silently disappear from Heeler's view.

*Prerequisites:* the Azure DevOps CLI installed and signed in to your organization (`az login`, then `az extension add --name azure-devops`), run by an account that can **manage Azure DevOps permissions** — a **Project Collection Administrator**.

First resolve the Service Principal's **subject descriptor**. The `--subject` value must be this `aadsp.…` form — *not* the application (client) ID or object ID (replace the display name with your app registration's name):

```bash
az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 \
  --url "https://vssps.dev.azure.com/{organization}/_apis/graph/serviceprincipals?api-version=7.1-preview.1" \
  --query "value[?displayName=='Heeler Azure DevOps'].descriptor" -o tsv
```

Then grant project visibility on the **Project** root token, code read + pull-request posting on the **Git Repositories** root token, and Service Hooks on the **ServiceHooks** root token:

```bash
# View project-level information (bit 1) — makes every project, current and
# future, visible to the Service Principal
az devops security permission update \
  --organization https://dev.azure.com/{organization} \
  --namespace-id 52d39943-cb85-4d7f-8fa8-c6baac873819 \
  --subject "{aadsp-descriptor}" \
  --token '$PROJECT' \
  --allow-bit 1 --merge true

# Code read + Contribute to pull requests (bit 16386 = Read 2 + PullRequestContribute 16384)
az devops security permission update \
  --organization https://dev.azure.com/{organization} \
  --namespace-id 2e9eb7ed-3c0a-47d4-87c1-0ffdd275fd87 \
  --subject "{aadsp-descriptor}" \
  --token "repoV2" \
  --allow-bit 16386 --merge true

# Service Hooks — View + Edit + Delete Subscriptions (bit 7)
az devops security permission update \
  --organization https://dev.azure.com/{organization} \
  --namespace-id cb594ebe-87dd-4fc9-ac2c-6a10a4c92046 \
  --subject "{aadsp-descriptor}" \
  --token "PublisherSecurity" \
  --allow-bit 7 --merge true
```

`$PROJECT` is the **Project** root token (`1` = View project-level information), `repoV2` the **Git Repositories** root token (`16386` = Read + Contribute to pull requests), and `PublisherSecurity` the **ServiceHooks** root token (`7` = View + Edit + Delete Subscriptions). All three apply org-wide and inherit to every project; Heeler never needs push or Publish Events.

**Simplest, via the portal.** Add the Service Principal to **Project Administrators** for each project (**Project settings → Permissions → Project Administrators → Members**), or to **Project Collection Administrators** (**Organization settings → Permissions**) for org-wide coverage. This grants code read, pull-request posting, and service-hook management in one step — easier to set and audit, but far more than Heeler needs.

{% hint style="info" %}
Permission changes on a Service Principal take a few minutes to propagate in Azure DevOps. If **Generate Webhook** fails with a permission error right after you grant this, wait a moment and retry.
{% endhint %}
{% endstep %}

{% step %}

### Finish in Heeler

Back in the Heeler wizard, enter:

* **Connection Name** — e.g. `Contoso Azure DevOps`.
* **Organization** — your Azure DevOps organization name (the `{organization}` in `https://dev.azure.com/{organization}`).
* **Tenant ID** — the Directory (tenant) ID from the **Register an app in Microsoft Entra** step.
* **Client ID** — the Application (client) ID from the **Register an app in Microsoft Entra** step.

Click **Save & Continue**. Heeler mints an app-only token to verify access, then advances to the **Webhook** step.
{% endstep %}

{% step %}

### Generate webhooks

Click **Generate Webhook**. Heeler creates **Service Hook** subscriptions for every project (code pushed, pull request created/updated/merged), then click **Next** to review the summary. New projects get subscriptions automatically on the next discovery cycle.
{% endstep %}

{% step %}

### Verify

Heeler begins harvesting your Azure DevOps **projects**, **repositories**, **teams and members**, and **pipelines**. Open **Catalog → Repositories** and confirm your repositories appear with analysis status. On the **Code Organizations** list, the connection is labelled **· Microsoft app**. Initial discovery can take up to an hour.
{% endstep %}
{% endstepper %}

## Ongoing maintenance

Heeler mints a fresh app-only access token as needed by presenting a signed assertion to Microsoft Entra; there is no refresh token, secret, or annual rotation to manage. The connection stays healthy as long as the Service Principal remains a member of the organization with the right access and the federated credential stays configured.

## Troubleshooting

<table><thead><tr><th width="330">Symptom</th><th>Fix</th></tr></thead><tbody><tr><td><strong>Unhealthy — federation misconfigured</strong></td><td>Microsoft Entra rejected Heeler's assertion. In the app registration's <strong>Federated credentials</strong>, confirm the Issuer, Subject, and Audience exactly match the values shown in the Heeler wizard (Audience must be <code>api://AzureADTokenExchange</code>).</td></tr><tr><td><strong>Unhealthy — app not found</strong></td><td>Verify the <strong>Tenant ID</strong> and <strong>Client ID</strong> entered in Heeler match the app registration's Directory (tenant) ID and Application (client) ID.</td></tr><tr><td><strong>Unhealthy — app disabled</strong></td><td>The app registration (or its enterprise application) is disabled in Entra. Re-enable it.</td></tr><tr><td><strong>Unhealthy — Service Principal not in organization</strong></td><td>Heeler authenticated to Entra but the Service Principal isn't a member of the org. Add it under <strong>Organization settings → Users</strong> with a <strong>Basic</strong> access level.</td></tr><tr><td><strong>Unhealthy — code read denied</strong></td><td>The Service Principal is in the org but can't read repositories. Re-run the <code>repoV2</code> root-token grant (bit 16386) in the <strong>Grant least-privilege permissions</strong> step — or, if you used the portal admin option, confirm it's in <strong>Project Administrators</strong> / <strong>Project Collection Administrators</strong>.</td></tr><tr><td><strong>Some projects or repositories missing from Heeler</strong></td><td>The Service Principal can read code but can't <em>see</em> every project: repository permissions don't grant project visibility, and Azure DevOps hides invisible projects (and all their repositories) from listing entirely. This typically affects projects created after setup, or projects not selected when the Service Principal was added. Heeler pauses repository sync rather than treating the hidden repositories as deleted. Re-run the <code>$PROJECT</code> root-token grant (bit 1) in the <strong>Grant least-privilege permissions</strong> step — it covers all current and future projects.</td></tr><tr><td><strong>Unhealthy — code comment not allowed</strong></td><td>The Service Principal can read code but can't post pull-request status or comments, so guardrails can't report. Its <code>repoV2</code> grant is missing the <strong>Contribute to pull requests</strong> bit — re-run it with <code>--allow-bit 16386</code> (Read 2 + PullRequestContribute 16384) in the <strong>Grant least-privilege permissions</strong> step.</td></tr><tr><td><strong>Service Principal has push access (added as a Contributor)</strong></td><td>Adding it with the default <strong>Project Contributors</strong> group also grants <strong>Contribute</strong> (push) — more than Heeler needs. Remove it under <strong>Project settings → Permissions → Contributors → Members</strong>, then add it under <strong>Readers → Members</strong>. The <strong>Inheritance</strong> toggle on the Security page does <em>not</em> remove group-granted permissions (they show as <strong>Allow (inherited)</strong>) — you must change the group membership.</td></tr><tr><td><strong>Webhook creation failed — no service hook subscriptions created</strong></td><td>The Service Principal lacks the Service Hooks <strong>Edit Subscriptions</strong> permission — see the <strong>Grant least-privilege permissions</strong> step. Newly granted Service Principal permissions take a few minutes to propagate in Azure DevOps, so wait a moment and retry.</td></tr><tr><td><strong>Unhealthy — tenant policy blocked</strong></td><td>Conditional Access or tenant policy is blocking the token request. Ask an Entra administrator to allow it.</td></tr></tbody></table>

## Related

* [Azure DevOps (App registration / federated) — CLI](/mrecEO40m5D6bt7Pq5pE/get-started/source-code-scm/azure-devops-app-registration/azure-devops-app-registration-cli.md) — the same setup, scripted from the command line.
* [Azure DevOps (PAT)](/mrecEO40m5D6bt7Pq5pE/get-started/source-code-scm/azure-devops.md) — the token-based alternative, and how the two compare.
* [Connect Your Code](/mrecEO40m5D6bt7Pq5pE/get-started/source-code-scm.md) — the common flow and network requirements.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.heeler.com/mrecEO40m5D6bt7Pq5pE/get-started/source-code-scm/azure-devops-app-registration.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
