---
title: "Register a Microsoft Graph Application"
description: "Azure portal walkthrough for creating the app registration that powers the Teams meeting pipeline"
---

# Register a Microsoft Graph Application

The Teams meeting pipeline reads meeting transcripts, recordings, and related artifacts from Microsoft Graph using **app-only** (daemon) authentication — no user sign-in, no interactive consent per meeting. That requires an Azure AD application registration with admin-consented application permissions.

This guide walks through:

1. Creating the app registration
2. Creating a client secret
3. Granting the Graph API permissions the pipeline needs
4. Admin-consenting those permissions
5. (Optional) Scoping the app to specific users with an Application Access Policy

You need **tenant admin rights** (or an admin to grant consent on your behalf) to finish this. Bookmark the values you collect — they go into `~/.hermes/.env` at the end.

## Prerequisites

- A Microsoft 365 tenant with Teams Premium or Teams licenses that produce meeting transcripts and recordings
- Admin access to the Azure portal at [entra.microsoft.com](https://entra.microsoft.com)
- A publicly reachable HTTPS endpoint for Graph change notifications (set up later, in the webhook listener step)

## Step 1: Create the App Registration

1. Sign in to [entra.microsoft.com](https://entra.microsoft.com) as a tenant admin.
2. Navigate to **Identity → Applications → App registrations**.
3. Click **New registration**.
4. Fill in:
   - **Name:** `Hermes Teams Meeting Pipeline` (or any name you'll recognize).
   - **Supported account types:** *Accounts in this organizational directory only (Single tenant)*.
   - **Redirect URI:** leave blank — app-only auth does not need one.
5. Click **Register**.

You'll land on the app's overview page. Copy two values:

- **Application (client) ID** → `MSGRAPH_CLIENT_ID`
- **Directory (tenant) ID** → `MSGRAPH_TENANT_ID`

## Step 2: Create a Client Secret

1. In the left nav, open **Certificates & secrets**.
2. Click **New client secret**.
3. **Description:** `hermes-graph-secret`. **Expires:** pick a value that matches your rotation policy (6-24 months is typical).
4. Click **Add**.
5. Copy the **Value** column immediately — it's only shown once. That value is `MSGRAPH_CLIENT_SECRET`.

> The **Secret ID** column is not the secret. You want the **Value** column.

## Step 3: Grant Graph API Permissions

The pipeline uses a minimum-viable set of application permissions. Add only what you need; each one widens what the app can read tenant-wide.

1. In the left nav, open **API permissions**.
2. Click **Add a permission** → **Microsoft Graph** → **Application permissions**.
3. Add the permissions from the table below that match what you want the pipeline to do.
4. After adding, click **Grant admin consent for `<your tenant>`**. The Status column should flip to a green checkmark for every permission.

### Required for transcript-first summaries

| Permission | What it lets the app do |
|------------|--------------------------|
| `OnlineMeetings.Read.All` | Read Teams online meeting metadata (subject, participants, join URL). |
| `OnlineMeetingTranscript.Read.All` | Read meeting transcripts generated by Teams. |

### Required for recording fallback (when a transcript is unavailable)

| Permission | What it lets the app do |
|------------|--------------------------|
| `OnlineMeetingRecording.Read.All` | Download Teams meeting recordings for offline STT processing. |
| `CallRecords.Read.All` | Resolve meetings from call records when only the join URL is known. |

### Required for outbound summary delivery (Graph mode only)

If `platforms.teams.extra.delivery_mode` is `graph`, the pipeline posts summaries into a Teams channel or chat via the Graph API. Skip these if you use `incoming_webhook` delivery mode instead.

| Permission | What it lets the app do |
|------------|--------------------------|
| `ChannelMessage.Send` | Post messages into Teams channels on behalf of the app. |
| `Chat.ReadWrite.All` | Post messages into 1:1 and group chats (only if you set `chat_id` as the delivery target). |

### Not recommended

- `OnlineMeetings.ReadWrite.All` / `Chat.ReadWrite` without `.All` — broader than the pipeline needs.
- Delegated permissions — the pipeline uses app-only (client-credentials) flow; delegated permissions won't work without user sign-in.

## Step 4: (Recommended) Scope the App with an Application Access Policy

By default, application permissions like `OnlineMeetings.Read.All` grant the app access to **every** meeting in the tenant. For partner demos and dev tenants that's fine; for production you almost certainly want to restrict which users' meetings the app can read.

Microsoft provides **Application Access Policies** for Teams exactly for this. The policy is a PowerShell-only surface; there's no portal UI for it.

From an admin PowerShell with the MicrosoftTeams module installed and connected (`Connect-MicrosoftTeams`):

```powershell
# Create a policy scoped to the Hermes app
New-CsApplicationAccessPolicy `
  -Identity "Hermes-Meeting-Pipeline-Policy" `
  -AppIds "<MSGRAPH_CLIENT_ID>" `
  -Description "Restrict Hermes meeting pipeline to allow-listed users"

# Grant the policy to specific users whose meetings the pipeline may read
Grant-CsApplicationAccessPolicy `
  -PolicyName "Hermes-Meeting-Pipeline-Policy" `
  -Identity "alice@example.com"

Grant-CsApplicationAccessPolicy `
  -PolicyName "Hermes-Meeting-Pipeline-Policy" `
  -Identity "bob@example.com"
```

Propagation can take up to 30 minutes after granting. Verify with:

```powershell
Test-CsApplicationAccessPolicy -Identity "alice@example.com" -AppId "<MSGRAPH_CLIENT_ID>"
```

Without the policy, **any** user's meetings are readable — that's what the permission technically grants. Don't skip this step on a production tenant.

## Step 5: Write the Credentials to Your Env File

Put the three values you collected into `~/.hermes/.env`:

```bash
MSGRAPH_TENANT_ID=<directory-tenant-id>
MSGRAPH_CLIENT_ID=<application-client-id>
MSGRAPH_CLIENT_SECRET=<client-secret-value>
```

Set file permissions so only you can read the secret:

```bash
chmod 600 ~/.hermes/.env
```

## Step 6: Verify the Token Flow

Hermes ships a Graph auth smoke-test. From your Hermes install:

```python
python -c "
import asyncio
from tools.microsoft_graph_auth import MicrosoftGraphTokenProvider
provider = MicrosoftGraphTokenProvider.from_env()
token = asyncio.run(provider.get_access_token())
print('Token acquired, length:', len(token))
print(provider.inspect_token_health())
"
```

A successful run prints a long token string and a health dict showing `cached: True` and an `expires_in_seconds` value near 3600. Failures produce a `MicrosoftGraphTokenError` with the Azure error code — the most common are:

| Azure error | Meaning | Fix |
|-------------|---------|-----|
| `AADSTS7000215: Invalid client secret` | Secret value mismatched or expired. | Generate a new secret in step 2; update `.env`. |
| `AADSTS700016: Application not found` | Wrong `MSGRAPH_CLIENT_ID` or wrong tenant. | Double-check the values from step 1 are from the same app. |
| `AADSTS90002: Tenant not found` | Typo in `MSGRAPH_TENANT_ID`. | Copy the Directory (tenant) ID from the app overview again. |
| `insufficient_claims` at call time (not token time) | Token acquires but Graph returns 401/403. | You skipped step 3 admin-consent, or added permissions but haven't re-consented. Revisit API permissions and click **Grant admin consent** again. |

## Rotating the Client Secret

Azure client secrets have a hard expiry. Before yours expires:

1. Create a second client secret in step 2 without deleting the first one.
2. Update `MSGRAPH_CLIENT_SECRET` in `~/.hermes/.env` with the new value.
3. Restart the gateway so the new secret is picked up: `hermes gateway restart`.
4. Verify with the smoke test above.
5. Delete the old secret from the Azure portal.

## Next Steps

Once credentials verify cleanly, continue with:

- **Webhook listener setup** — stand up the `msgraph_webhook` gateway platform that receives Graph change notifications.
- **Pipeline configuration** — configure the Teams meeting pipeline runtime and operator CLI.
- **Outbound delivery** — wire summaries back into a Teams channel or chat.

Those pages land alongside the PRs that add the corresponding runtime. This credentials setup is a standalone prerequisite and is safe to complete in advance.
