# Microsoft Graph API Contracts for Teams Phone CLI

**Version:** 1.7
**Date:** 2026-01-27
**Status:** Draft - VERIFIED (includes PowerShell output schemas + locationId enforcement clarification)

This document lists all Microsoft Graph API operations required for the Teams Phone CLI MVP, with **verified** status based on actual Microsoft documentation research.

---

## Critical Findings

### What EXISTS and is DOCUMENTED

| API Area | Status | Notes |
|----------|--------|-------|
| User Configurations | Beta - Documented | Full read access to user voice config |
| Number Assignments (READ) | Beta - Documented | List and get phone numbers |
| Number Assign/Unassign | Beta - Documented | Async operations with 202 response |
| Number Update | Beta - Documented | Update location, network site |
| Policy Assignment | Beta - Documented | Assign policies to users |

### What does NOT EXIST or is NOT DOCUMENTED

| API Area | Status | Workaround |
|----------|--------|------------|
| Emergency Locations API | **NOT AVAILABLE** | PowerShell CSV export (see Section 7) |
| Number Reserve/Unreserve | **NOT AVAILABLE** | No equivalent exists - dropped from MVP |
| Policy List by Type | **Not Found** | PowerShell CSV export (see Section 7) |

### ⚠️ Important Schema vs Enforcement Discrepancy

| API Endpoint | Parameter | Schema Says | Reality |
|--------------|-----------|-------------|---------|
| `assignNumber` | `locationId` | Optional | **REQUIRED for Calling Plan** (backend enforcement due to E911 regulations). Optional for Direct Routing. See Section 2.3 for details. |

**Impact**: CLI must detect number type and enforce location requirement based on numberType, not just the API schema.

---

## Summary

| Category | Graph API | CSV Export | Dropped |
|----------|-----------|------------|---------|
| Users | 3 read | - | - |
| Numbers | 3 read, 3 write | - | reserve/unreserve |
| Policies | 1 read, 1 write | list by type | - |
| Locations | - | list, show, search | - |
| Directory | 2 read | - | - |
| **Total** | **9 read, 4 write** | **4 ops** | **2 ops** |

---

## 1. User Operations - VERIFIED

### 1.1 List User Configurations
**CLI Commands:** `users list`
**Status:** VERIFIED

| Property | Value |
|----------|-------|
| **Endpoint** | `GET /admin/teams/userConfigurations` |
| **API Version** | Beta |
| **Permission** | `TeamsUserConfiguration.Read.All` |
| **Pagination** | Yes (`$top`, `$skip`) |

**Documentation:** [List userConfigurations](https://learn.microsoft.com/en-us/graph/api/teamsadministration-teamsadminroot-list-userconfigurations?view=graph-rest-beta)

**Query Parameters:**
- `$top` - Page size
- `$skip` - Skip count
- `$filter` - OData filter
- `$select` - Field selection
- `$expand` - Expand related resources
- `$orderBy` - Sort order

**Response Properties:**
```json
{
  "id": "string (GUID)",
  "userPrincipalName": "string",
  "tenantId": "string",
  "accountType": "user|resourceAccount|guest|sfbOnPremUser|unknown",
  "isEnterpriseVoiceEnabled": "boolean",
  "featureTypes": ["Teams", "CallingPlan", ...],
  "telephoneNumbers": [
    {
      "telephoneNumber": "+13235533696",
      "assignmentCategory": "primary"
    }
  ],
  "effectivePolicyAssignments": [
    {
      "policyType": "TeamsCallingPolicy",
      "policyAssignment": {
        "displayName": "string",
        "assignmentType": "direct|group",
        "policyId": "string",
        "groupId": "string"
      }
    }
  ],
  "createdDateTime": "ISO 8601",
  "modifiedDateTime": "ISO 8601"
}
```

**Contract Status:** [x] Verified [x] Documented [ ] Tested

---

### 1.2 Get User Configuration
**CLI Commands:** `users show <user>`
**Status:** VERIFIED

| Property | Value |
|----------|-------|
| **Endpoint** | `GET /admin/teams/userConfigurations/{teamsUserConfigurationId}` |
| **API Version** | Beta |
| **Permission** | `TeamsUserConfiguration.Read.All` |

**Documentation:** [Get teamsUserConfiguration](https://learn.microsoft.com/en-us/graph/api/teamsadministration-teamsuserconfiguration-get?view=graph-rest-beta)

**Path Parameters:**
- `teamsUserConfigurationId` - User's Entra object ID (GUID)

**Query Parameters:**
- `$select` - Field selection
- `$expand` - Expand related resources

**Response:** Same as 1.1 but single object

**Contract Status:** [x] Verified [x] Documented [ ] Tested

---

### 1.3 Search/Resolve Users
**CLI Commands:** `users search <query>`, user resolution for other commands
**Status:** VERIFIED (V1.0)

| Property | Value |
|----------|-------|
| **Endpoint** | `GET /users` |
| **API Version** | V1.0 |
| **Permission** | `User.Read.All` |

**Query Parameters:**
- `$search` - Search expression (requires `ConsistencyLevel: eventual` header)
- `$filter` - OData filter (startsWith, eq)
- `$top` - Result limit
- `$select` - Field selection

**Notes:**
- To search by phone number, use userConfigurations endpoint with filter
- Can filter by `userPrincipalName`, `displayName`, `id`

**Contract Status:** [x] Verified [x] Documented [ ] Tested

---

## 2. Phone Number Operations - VERIFIED

### 2.1 List Number Assignments
**CLI Commands:** `numbers list`
**Status:** VERIFIED

| Property | Value |
|----------|-------|
| **Endpoint** | `GET /admin/teams/telephoneNumberManagement/numberAssignments` |
| **API Version** | Beta |
| **Permission** | `TeamsTelephoneNumber.Read.All` or `TeamsTelephoneNumber.ReadWrite.All` |
| **Pagination** | Yes (`$top` max 1000, default 500) |

**Documentation:** [List numberAssignments](https://learn.microsoft.com/en-us/graph/api/teamsadministration-telephonenumbermanagementroot-list-numberassignments?view=graph-rest-beta)

**Query Parameters:**
- `$top` - Page size (default 500, max 1000)
- `$skip` - Skip count
- `$filter` - OData filter (**cannot filter by `id` or `city`**)

**Filter Examples:**
```
$filter=numberType eq 'DirectRouting'
$filter=assignmentStatus eq 'unassigned' and capabilities/any(c:c eq 'userAssignment')
$filter=telephoneNumber eq '+12052582895'
```

**Response Properties:**
```json
{
  "id": "string",
  "telephoneNumber": "+12052582895",
  "operatorId": "UUID",
  "numberType": "directRouting|callingPlan|operatorConnect",
  "activationState": "activated|assignmentPending|assignmentFailed|updatePending|updateFailed",
  "capabilities": ["userAssignment", "conferenceAssignment", "voiceApplicationAssignment"],
  "locationId": "string|null",
  "civicAddressId": "string|null",
  "networkSiteId": "string|null",
  "assignmentTargetId": "string|null (user ObjectId)",
  "assignmentCategory": "primary|private|alternate|null",
  "assignmentStatus": "unassigned|userAssigned|conferenceAssigned|voiceApplicationAssigned",
  "portInStatus": "completed|firmOrderCommitmentAccepted|null",
  "isoCountryCode": "US",
  "city": "string|null",
  "numberSource": "online|onPremises",
  "supportedCustomerActions": ["locationUpdate"],
  "reverseNumberLookupOptions": []
}
```

**Contract Status:** [x] Verified [x] Documented [ ] Tested

---

### 2.2 Get Number Assignment
**CLI Commands:** `numbers show <number>`
**Status:** VERIFIED

| Property | Value |
|----------|-------|
| **Endpoint** | `GET /admin/teams/telephoneNumberManagement/numberAssignments/{numberAssignmentId}` |
| **API Version** | Beta |
| **Permission** | `TeamsTelephoneNumber.Read.All` |

**Documentation:** [Get numberAssignment](https://learn.microsoft.com/en-us/graph/api/teamsadministration-numberassignment-get?view=graph-rest-beta)

**Path Parameters:**
- `numberAssignmentId` - The ID from list results (NOT the phone number itself)

**Notes:**
- Must first search by phone number to get the ID, then fetch by ID
- Filter: `$filter=telephoneNumber eq '+12065551234'`

**Contract Status:** [x] Verified [x] Documented [ ] Tested

---

### 2.3 Assign Number to User
**CLI Commands:** `numbers assign <user> <number> [location]`
**Status:** VERIFIED

| Property | Value |
|----------|-------|
| **Endpoint** | `POST /admin/teams/telephoneNumberManagement/numberAssignments/assignNumber` |
| **API Version** | Beta |
| **Permission** | `TeamsTelephoneNumber.ReadWrite.All` |
| **Response** | `202 Accepted` (async operation) |

**Documentation:** [numberAssignment: assignNumber](https://learn.microsoft.com/en-us/graph/api/teamsadministration-numberassignment-assignnumber?view=graph-rest-beta)

**Request Body:**
```json
{
  "telephoneNumber": "12061234567",
  "assignmentTargetId": "94ec379d-30a2-4cdb-a377-75e42f7a61e5",
  "numberType": "directRouting",
  "assignmentCategory": "primary",
  "locationId": "93cb8a70-b4af-41df-9928-d07607e21776"
}
```

**Request Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `telephoneNumber` | String | **Yes** | The telephone number to assign |
| `assignmentTargetId` | String | **Yes** | User's Entra object ID |
| `numberType` | Enum | **Yes** | `directRouting`, `callingPlan`, or `operatorConnect` |
| `assignmentCategory` | Enum | No | `primary` (default) or `private` |
| `locationId` | String | No | Emergency location ID |

**Response:**
```http
HTTP/1.1 202 Accepted
Location: https://graph.microsoft.com/beta/admin/teams/telephoneNumberManagement/operations('...')
```

**Notes:**
- This is an **async operation** - returns 202 with Location header to check status
- Phone number format: digits only, no `+` prefix in request

**⚠️ CRITICAL - locationId Requirement by Number Type:**

| Number Type | locationId Required? | Enforcement |
|-------------|---------------------|-------------|
| **Calling Plan (US/Canada)** | **YES** | Backend validation enforces E911 regulations. API call FAILS without locationId. |
| **Calling Plan (EMEA)** | **YES** | Required at number acquisition/porting. |
| **Direct Routing** | **NO** | Truly optional. Can assign number without location and set later via `updateNumber`. |
| **Operator Connect** | **Varies** | Provider-dependent (typically required). |

**API Schema vs Reality:**
- The Graph API documentation marks `locationId` as **optional** in the request schema
- However, the **backend enforces** locationId for Calling Plan numbers due to regulatory requirements
- TAC and PowerShell (`Set-CsPhoneNumberAssignment`) prevent submission without locationId for Calling Plan
- CLI should detect number type and enforce location requirement for Calling Plan, allow optional for Direct Routing

**Regulatory Context:**
Per [Microsoft Teams Calling Plan considerations](https://learn.microsoft.com/en-us/microsoftteams/considerations-calling-plan):
> "Each Calling Plan user is automatically enabled for emergency calling and is required to have a registered emergency address associated with their assigned telephone number."

**Contract Status:** [x] Verified [x] Documented [ ] Tested

---

### 2.4 Unassign Number from User
**CLI Commands:** `numbers unassign <number>`
**Status:** VERIFIED

| Property | Value |
|----------|-------|
| **Endpoint** | `POST /admin/teams/telephoneNumberManagement/numberAssignments/unassignNumber` |
| **API Version** | Beta |
| **Permission** | `TeamsTelephoneNumber.ReadWrite.All` |
| **Response** | `202 Accepted` (async operation) |

**Documentation:** [numberAssignment: unassignNumber](https://learn.microsoft.com/en-us/graph/api/teamsadministration-numberassignment-unassignnumber?view=graph-rest-beta)

**Request Body:**
```json
{
  "telephoneNumber": "12061234567",
  "numberType": "directRouting"
}
```

**Request Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `telephoneNumber` | String | **Yes** | The telephone number to unassign |
| `numberType` | Enum | **Yes** | `directRouting`, `callingPlan`, or `operatorConnect` |

**Response:**
```http
HTTP/1.1 202 Accepted
Location: https://graph.microsoft.com/beta/admin/teams/telephoneNumberManagement/operations('...')
```

**Notes:**
- This is an **async operation** - returns 202 with Location header to check status
- Does NOT require user ID - just the phone number and type

**Contract Status:** [x] Verified [x] Documented [ ] Tested

---

### 2.5 Update Number (Location/Settings)
**CLI Commands:** Could support `numbers update <number> --location <id>`
**Status:** VERIFIED

| Property | Value |
|----------|-------|
| **Endpoint** | `POST /admin/teams/telephoneNumberManagement/numberAssignments/updateNumber` |
| **API Version** | Beta |
| **Permission** | `TeamsTelephoneNumber.ReadWrite.All` |
| **Response** | `200 OK` |

**Documentation:** [numberAssignment: updateNumber](https://learn.microsoft.com/en-us/graph/api/teamsadministration-numberassignment-updatenumber?view=graph-rest-beta)

**Request Body:**
```json
{
  "telephoneNumber": "+12061234567",
  "locationId": "93cb8a70-b4af-41df-9928-d07607e21776"
}
```

**Request Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `telephoneNumber` | String | **Yes** | The telephone number to update |
| `locationId` | String | No | Emergency location ID. Empty string clears it; null/omit preserves existing |
| `networkSiteId` | String | No | Network site ID. Empty string clears it; null/omit preserves existing |
| `reverseNumberLookupOptions` | String[] | No | RNL options, e.g., `["skipInternalVoip"]` |

**Response:** `200 OK` with empty body

**Notes:**
- Synchronous operation (unlike assign/unassign)
- Can update location without reassigning number
- Use empty string `""` to clear a value

**Contract Status:** [x] Verified [x] Documented [ ] Tested

---

### 2.6 Get Operation Status (Async Polling)
**CLI Commands:** Internal use for `numbers assign`, `numbers unassign`
**Status:** VERIFIED

| Property | Value |
|----------|-------|
| **Endpoint** | `GET /admin/teams/telephoneNumberManagement/operations/{operationId}` |
| **API Version** | Beta |
| **Permission** | `TeamsTelephoneNumber.Read.All` |

**Documentation:** [Get telephoneNumberLongRunningOperation](https://learn.microsoft.com/en-us/graph/api/teamsadministration-telephonenumberlongrunningoperation-get?view=graph-rest-beta)

**Path Parameters:**
- `operationId` - The operation ID from the `Location` header (base64 encoded)

**Response Properties:**
```json
{
  "@odata.type": "#microsoft.graph.teamsAdministration.telephoneNumberLongRunningOperation",
  "id": "QXNzaWdubWVudHw2Y2E4Yjc0Ni0...",
  "createdDateTime": "2025-02-03T22:03:26Z",
  "status": "succeeded",
  "numbers": [
    {
      "resourceLocation": "https://graph.microsoft.com/beta/.../numberAssignments/{id}",
      "status": "succeeded"
    }
  ]
}
```

**Status Values (`longRunningOperationStatus`):**
| Value | Terminal | Description |
|-------|----------|-------------|
| `notStarted` | No | Operation queued |
| `running` | No | In progress |
| `succeeded` | **Yes** | Completed successfully |
| `failed` | **Yes** | Operation failed |
| `skipped` | **Yes** | Operation skipped |
| `unknownFutureValue` | - | Future-proofing |

**Polling Pattern:**
1. POST to `assignNumber`/`unassignNumber` → `202 Accepted` + `Location` header
2. Extract operation ID from Location: `.../operations('{id}')`
3. Poll `GET /admin/teams/telephoneNumberManagement/operations/{id}`
4. Wait for `status` ∈ {`succeeded`, `failed`, `skipped`}
5. On success: `numbers[].resourceLocation` points to the updated number

**Recommended Polling Interval:** 2-5 seconds (operations typically complete quickly)

**Contract Status:** [x] Verified [x] Documented [ ] Tested

---

### 2.7 Reserve/Unreserve Number
**CLI Commands:** `numbers reserve <number>`, `numbers unreserve <number>`
**Status:** NOT AVAILABLE

No Graph API or PowerShell equivalent found for reserving numbers. This may be a Teams Admin Center-only feature or may not exist.

**Recommendation:** Drop this feature from MVP.

**Contract Status:** [ ] Verified [ ] Documented [ ] Tested - **Feature does not exist**

---

## 3. Policy Operations - PARTIALLY VERIFIED

### 3.1 List Policies by Type
**CLI Commands:** `policies list [type]`
**Status:** NOT FOUND

No dedicated Graph API endpoint found for listing available policies by type. The policy names are referenced in assignments but there's no "list all calling policies" endpoint documented.

**MVP Solution:** PowerShell CSV export script (see Section 7)
- User runs PowerShell script to export policies to CSV
- CLI reads CSV for validation, display, and tab completion
- Degrades gracefully if CSV missing

**Contract Status:** [ ] Verified [ ] Documented [ ] Tested - **NOT AVAILABLE via Graph API**

---

### 3.2 Get User Policy Assignments
**CLI Commands:** `policies show <user>`
**Status:** VERIFIED (via userConfigurations)

User policy assignments are included in the `userConfigurations` endpoint response under `effectivePolicyAssignments`.

**Voice-Specific Policy Types (PRD Requirements) - VERIFIED:**

| PRD Name | Graph policyType | Batch Assignment | Status |
|----------|------------------|------------------|--------|
| Calling Policy | `TeamsCallingPolicy` | ✓ Supported | **VERIFIED** |
| Voicemail Policy | `TeamsVoicemailPolicy` | ✗ Not supported | Likely returned* |
| Emergency Calling Policy | `TeamsEmergencyCallingPolicy` | ✓ Supported | **VERIFIED** |
| Emergency Call Routing Policy | `TeamsEmergencyCallRoutingPolicy` | ✓ Supported | **VERIFIED** |
| Caller ID Policy | `CallingLineIdentity` | ✓ Supported | **VERIFIED** |
| Dial Plan | `TenantDialPlan` | ✓ Supported | **VERIFIED** |
| Voice Routing Policy | `OnlineVoiceRoutingPolicy` | ✓ Supported | **VERIFIED** |

*TeamsVoicemailPolicy follows the same pattern but is not in the batch assignment list. It can be assigned individually via `Grant-CsTeamsVoicemailPolicy`.

**Other Policy Types Also Returned:**
- `TeamsMeetingPolicy`
- `TeamsMessagingPolicy`
- `TeamsUpdateManagementPolicy`
- `TeamsAppSetupPolicy`
- `TeamsAppPermissionPolicy`
- And others (20+ types supported)

**Example Response:**
```json
"effectivePolicyAssignments": [
  {
    "policyType": "TeamsCallingPolicy",
    "policyAssignment": {
      "displayName": "AllOn",
      "assignmentType": "group",
      "policyId": "f7593e81-772a-4455-88a3-fc9e5ebc1e4a",
      "groupId": "75ae6229-35fe-4b01-ae7f-7c50439d239c"
    }
  }
]
```

**Contract Status:** [x] Verified [x] Documented [ ] Tested

---

### 3.3 Assign Policy to User
**CLI Commands:** `policies assign <user> <type> <name>`
**Status:** VERIFIED

| Property | Value |
|----------|-------|
| **Endpoint** | `POST /admin/teams/policy/userAssignments/assign` |
| **API Version** | Beta |
| **Permission** | `TeamsPolicyUserAssign.ReadWrite.All` |

**Documentation:** [teamsPolicyUserAssignment: assign](https://learn.microsoft.com/en-us/graph/api/teamsadministration-teamspolicyuserassignment-assign?view=graph-rest-beta)

**Request Body:**
```json
{
  "value": [
    {
      "@odata.type": "#microsoft.graph.teamsAdministration.teamsPolicyUserAssignment",
      "userId": "5c802b19-3600-83f1-1767-7b9edc7f38ab",
      "policyType": "TeamsMeetingPolicy",
      "policyId": "VnMAaN3X2X1B9tEHx1CJWfC76PSaKEzA4NoUuqIMRUo"
    }
  ]
}
```

**Response:** `204 No Content` on success

**Supported Policy Types (VERIFIED via batch assignment docs):**

Voice-related types:
- `TeamsCallingPolicy`
- `TeamsEmergencyCallingPolicy`
- `TeamsEmergencyCallRoutingPolicy`
- `CallingLineIdentity`
- `TenantDialPlan`
- `OnlineVoiceRoutingPolicy`

Other types:
- `TeamsMeetingPolicy`, `TeamsMessagingPolicy`, `TeamsAppSetupPolicy`, `TeamsAppPermissionPolicy`, `TeamsCallParkPolicy`, `TeamsChannelsPolicy`, `TeamsEducationAssignmentsAppPolicy`, `TeamsMeetingBroadcastPolicy`, `TeamsTemplatePermissionPolicy`, `TeamsUpdateManagementPolicy`, `TeamsUpgradePolicy`, `TeamsVerticalPackagePolicy`, `TeamsVideoInteropServicePolicy`, `ExternalAccessPolicy`

**Note:** `TeamsVoicemailPolicy` is NOT in the batch assignment list; use `Grant-CsTeamsVoicemailPolicy` via PowerShell.

**Contract Status:** [x] Verified [x] Documented [ ] Tested

---

## 4. Emergency Location Operations - NOT AVAILABLE

### 4.1-4.3 All Location Operations
**CLI Commands:** `locations list`, `locations show`, `locations search`
**Status:** NOT AVAILABLE VIA GRAPH API

**Finding:** There is **NO Microsoft Graph API endpoint** for managing emergency locations. The `numberAssignment` resource contains `locationId` and `civicAddressId` fields, but there is no corresponding API to:
- List emergency locations
- Get location details
- Search locations
- Create/update/delete locations

**Current Management Methods:**
1. **Teams Admin Center** - Web UI only
2. **PowerShell** cmdlets:
   - `New-CsOnlineLisLocation` - Create location
   - `Set-CsOnlineLisCivicAddress` - Modify address
   - `Get-CsOnlineLisCivicAddress` - List addresses
   - `Remove-CsOnlineLisCivicAddress` - Remove address

**Impact on CLI:**
- Cannot implement `locations list/show/search` commands via Graph API
- Cannot validate location IDs before number assignment in real-time

**MVP Solution:** PowerShell CSV export script (see Section 7)
- User runs PowerShell script to export locations to CSV
- CLI reads CSV for validation and display
- Degrades gracefully if CSV missing

**Contract Status:** [ ] Verified [ ] Documented [ ] Tested - **NOT AVAILABLE via Graph API**

---

## 5. Directory Operations - VERIFIED

### 5.1 Get User by ID/UPN
**Status:** VERIFIED (V1.0)

| Property | Value |
|----------|-------|
| **Endpoint** | `GET /users/{id}` |
| **API Version** | V1.0 |
| **Permission** | `User.Read.All` |

Standard Graph API - well documented.

**Contract Status:** [x] Verified [x] Documented [ ] Tested

---

### 5.2 Get Organization/Tenant Info
**Status:** VERIFIED (V1.0)

| Property | Value |
|----------|-------|
| **Endpoint** | `GET /organization` |
| **API Version** | V1.0 |
| **Permission** | `Directory.Read.All` |

Standard Graph API - well documented.

**Contract Status:** [x] Verified [x] Documented [ ] Tested

---

## 6. Batch Operations

### 6.1 Batch Request
**Status:** VERIFIED - Works with Beta Teams Admin APIs

| Property | Value |
|----------|-------|
| **Endpoint** | `POST /beta/$batch` |
| **API Version** | Beta (REQUIRED for Teams Admin APIs) |
| **Permission** | Same as individual requests |
| **Max Requests** | 20 per batch |

**Documentation:** [JSON Batching](https://learn.microsoft.com/en-us/graph/json-batching)

**Critical Finding - Version Compatibility:**

| Batch Endpoint | Can Include | Cannot Include |
|----------------|-------------|----------------|
| `v1.0/$batch` | v1.0 endpoints only | Beta endpoints (returns 405) |
| `beta/$batch` | Beta endpoints | - |

**For Teams Phone CLI:** Since all `/admin/teams/*` endpoints are beta-only, we MUST use `https://graph.microsoft.com/beta/$batch`.

**Request Format:**
```json
POST https://graph.microsoft.com/beta/$batch
Content-Type: application/json

{
  "requests": [
    {
      "id": "1",
      "method": "GET",
      "url": "/admin/teams/userConfigurations/user-id-1"
    },
    {
      "id": "2",
      "method": "GET",
      "url": "/admin/teams/userConfigurations/user-id-2"
    },
    {
      "id": "3",
      "method": "GET",
      "url": "/admin/teams/telephoneNumberManagement/numberAssignments?$filter=telephoneNumber eq '+12065551234'"
    }
  ]
}
```

**Required Properties per Request:**
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `id` | String | **Yes** | Unique identifier for correlating responses |
| `method` | String | **Yes** | HTTP method (GET, POST, etc.) |
| `url` | String | **Yes** | Relative URL (not full URL) |
| `headers` | Object | When body present | Headers including Content-Type |
| `body` | Object | For POST/PATCH | Request body |
| `dependsOn` | String[] | No | IDs of requests that must complete first |

**Response Format:**
```json
{
  "responses": [
    {
      "id": "1",
      "status": 200,
      "headers": { "Content-Type": "application/json" },
      "body": { /* userConfiguration data */ }
    },
    {
      "id": "2",
      "status": 200,
      "body": { /* userConfiguration data */ }
    },
    {
      "id": "3",
      "status": 200,
      "body": { "value": [ /* numberAssignment data */ ] }
    }
  ]
}
```

**Error Handling:**
- Batch response `200` = batch parsed successfully (not individual success)
- Individual requests have their own status codes
- Partial failures are possible - one failure doesn't stop others
- Responses may arrive in different order than requests (use `id` to correlate)

| Status | Meaning |
|--------|---------|
| `200` | Request succeeded |
| `403` | Insufficient permissions |
| `405` | Method not allowed (version mismatch) |
| `424` | Failed dependency (dependsOn failed) |
| `429` | Throttling limit exceeded |

**Use Cases for CLI:**
1. **Bulk user lookup** - Fetch multiple userConfigurations in one call
2. **Number search** - Look up multiple phone numbers simultaneously
3. **Export operations** - Reduce API calls when exporting large datasets

**Limitations:**
- Cannot mix v1.0 and beta endpoints in same batch
- 20 requests max per batch
- Each request still counts against throttling limits
- Sequential dependencies via `dependsOn` reduce parallelism benefits

**Contract Status:** [x] Verified [x] Documented [ ] Tested

---

## Required Permissions Summary (Updated)

| Permission | Type | Purpose | Verified |
|------------|------|---------|----------|
| `TeamsUserConfiguration.Read.All` | Application | Read user voice config | Yes |
| `TeamsTelephoneNumber.Read.All` | Application | Read phone inventory | Yes |
| `TeamsTelephoneNumber.ReadWrite.All` | Application | Assign/unassign/update numbers | Yes |
| `TeamsPolicyUserAssign.ReadWrite.All` | Application | Assign policies | Yes |
| `User.Read.All` | Application | User profiles | Yes |
| `Directory.Read.All` | Application | Tenant info | Yes |

**Note:** `TeamsEmergencyLocation.Read.All` permission does not appear to exist - emergency locations are not exposed via Graph API.

---

## Remaining Issues for MVP

### Resolved via PowerShell CSV Export

1. **Emergency Locations** - Solved via CSV export (see Section 7)
2. **Policy List** - Solved via CSV export (see Section 7)

### Dropped from MVP

1. **Reserve/Unreserve** - Feature does not exist anywhere; dropped

### Implementation Required

1. ~~**Async operations**~~ - VERIFIED: Schema documented in section 2.6; implement polling logic
2. ~~**Batch operations**~~ - VERIFIED: Use `beta/$batch` endpoint; documented in section 6.1
3. ~~**PowerShell output schemas**~~ - VERIFIED: Cmdlet outputs documented in section 7
4. **PowerShell export script** - Provide script for users to generate CSV files

---

## Recommendations

### Recommended Approach: Graph API First

With assign/unassign/update now verified, the recommended approach is:

**Users:** Full functionality via Graph API
- `users list` - userConfigurations endpoint
- `users show` - userConfigurations endpoint
- `users search` - /users endpoint + userConfigurations

**Numbers:** Full functionality via Graph API
- `numbers list` - numberAssignments endpoint
- `numbers show` - numberAssignments with filter
- `numbers search` - numberAssignments with filter
- `numbers assign` - assignNumber endpoint (async)
- `numbers unassign` - unassignNumber endpoint (async)
- ~~`numbers reserve`~~ - Drop (feature doesn't exist)
- ~~`numbers unreserve`~~ - Drop (feature doesn't exist)

**Policies:** Full functionality via Graph + CSV
- `policies show` - via userConfigurations effectivePolicyAssignments
- `policies assign` - userAssignments/assign endpoint
- `policies list` - Read from policies.csv (PowerShell export)

**Locations:** Full functionality via CSV
- `locations list` - Read from locations.csv (PowerShell export)
- `locations show` - Read from locations.csv
- `locations search` - Read from locations.csv
- CLI validates locationId against CSV when assigning numbers

**Tenants:** Full functionality
- All tenant management is local config + Graph validation

**Auth:** Full functionality
- MSAL-based, no Graph dependency for auth itself

---

## Implementation Notes

### Async Operations (assignNumber, unassignNumber) - VERIFIED

Both operations return `202 Accepted` with a `Location` header pointing to an operation status URL:
```
Location: https://graph.microsoft.com/beta/admin/teams/telephoneNumberManagement/operations('{operationId}')
```

**Implementation pattern:**
1. POST to assign/unassign endpoint
2. Extract operation ID from Location header
3. Poll the operations endpoint until status is complete or failed
4. Return result to user

**Operations Endpoint Response (VERIFIED):**
```json
{
  "id": "QXNzaWdubWVudHw2Y2E4Yjc0Ni0...",
  "createdDateTime": "2025-02-03T22:03:26Z",
  "status": "succeeded",  // notStarted | running | succeeded | failed | skipped
  "numbers": [
    {
      "resourceLocation": "https://graph.microsoft.com/beta/.../numberAssignments/{id}",
      "status": "succeeded"
    }
  ]
}
```

**Terminal States:** `succeeded`, `failed`, `skipped`

### Phone Number Format

- **assignNumber/unassignNumber:** Digits only, no `+` prefix (e.g., `12061234567`)
- **updateNumber:** With `+` prefix (e.g., `+12061234567`)
- **numberAssignments list/get:** Returns with `+` prefix

**Implementation:** Normalize phone numbers to handle both formats

---

## 7. PowerShell CSV Export (MVP Solution)

### Overview

Since Graph API does not expose emergency locations or policy listings, the MVP uses a **PowerShell export script** that users run periodically to generate CSV reference files. The CLI reads these files for validation, display, and tab completion.

**Key Principle:** Locations and policies are admin-configured and rarely change. A static export is acceptable for MVP.

### Architecture

```
┌─────────────────────────────────────────────────────────┐
│  One-time / Periodic Setup                              │
│                                                         │
│  User runs: Export-TeamsPhoneData.ps1                   │
│                                                         │
│  PowerShell Script:                                     │
│  ├── Connect-MicrosoftTeams (cert/token auth)           │
│  ├── Get-CsOnlineLisCivicAddress  → locations.csv       │
│  ├── Get-CsOnlineLisLocation      → locations.csv       │
│  ├── Get-CsTeamsCallingPolicy     → policies.csv        │
│  ├── Get-CsOnlineVoicemailPolicy  → policies.csv        │
│  ├── Get-CsCallingLineIdentity    → policies.csv        │
│  ├── Get-CsTenantDialPlan         → policies.csv        │
│  ├── Get-CsOnlineVoiceRoutingPolicy → policies.csv      │
│  ├── Get-CsTeamsEmergencyCallingPolicy → policies.csv   │
│  └── Get-CsTeamsEmergencyCallRoutingPolicy → policies   │
└──────────────────────┬──────────────────────────────────┘
                       ↓
                ~/.teams-phone/cache/
                ├── locations.csv
                ├── policies.csv
                └── export-metadata.json
                       ↓
┌──────────────────────┴──────────────────────────────────┐
│  CLI Runtime (Graph API only)                           │
│                                                         │
│  • Reads CSVs for validation and display                │
│  • Warns if files missing or stale                      │
│  • Still works without CSVs (degraded mode)             │
└─────────────────────────────────────────────────────────┘
```

### PowerShell Cmdlet Output Schemas - VERIFIED

The following schemas were verified via Microsoft documentation research (2026-01-27).

#### Get-CsOnlineLisLocation Output

| Property | Type | Description |
|----------|------|-------------|
| `LocationId` | GUID | Unique location identifier (used in number assignment) |
| `CivicAddressId` | GUID | Parent civic address ID |
| `Location` | String | Location name (e.g., "3rd Floor Cafe", "Main Lobby") |
| `Description` | String | Administrator-defined description |
| `City` | String | City name |
| `CompanyName` | String | Organization name |
| `CountryOrRegion` | String | Country code (e.g., "US") |
| `StateOrProvince` | String | State/province code |
| `PostalCode` | String | Postal/ZIP code |
| `HouseNumber` | String | Street number |
| `HouseNumberSuffix` | String | Street number suffix (e.g., "A", "B") |
| `PreDirectional` | String | Directional prefix (e.g., "NE") |
| `StreetName` | String | Street name |
| `StreetSuffix` | String | Street type (e.g., "Ave", "St", "Blvd") |
| `PostDirectional` | String | Directional suffix (e.g., "NE") |
| `Elin` | String | Emergency Location Identification Number |
| `Latitude` | Decimal | Geographic latitude |
| `Longitude` | Decimal | Geographic longitude |

#### Get-CsOnlineLisCivicAddress Output

| Property | Type | Description |
|----------|------|-------------|
| `CivicAddressId` | GUID | Unique civic address identifier |
| `CompanyName` | String | Organization name (required) |
| `Description` | String | Administrator-defined description |
| `HouseNumber` | String | Street number |
| `HouseNumberSuffix` | String | e.g., "A", "B" |
| `PreDirectional` | String | e.g., "NE" |
| `StreetName` | String | Street name |
| `StreetSuffix` | String | e.g., "Ave", "St" |
| `PostDirectional` | String | e.g., "NE" |
| `City` | String | City name |
| `CityAlias` | String | City alias |
| `StateOrProvince` | String | State/province code |
| `PostalCode` | String | Postal/ZIP code |
| `CountryOrRegion` | String | Country code (required) |
| `Latitude` | Decimal | Geographic latitude (required) |
| `Longitude` | Decimal | Geographic longitude (required) |
| `DefaultLocationId` | GUID | Default location for this address |

#### Policy Cmdlet Common Output Pattern

All policy cmdlets return objects with these common properties:

| Property | Type | Description |
|----------|------|-------------|
| `Identity` | String | Policy name (e.g., "Global", "AllowCalling") |
| `Description` | String | Administrator-defined description |

**Policy-Specific Additional Properties:**

| Cmdlet | Key Additional Properties |
|--------|---------------------------|
| `Get-CsTeamsCallingPolicy` | AllowPrivateCalling, AllowVoicemail, AllowCallForwardingToPhone, AllowCallGroups, AllowDelegation, AllowCallRedirect, BusyOnBusyEnabledType, etc. |
| `Get-CsOnlineVoicemailPolicy`* | EnableTranscription, EnableTranscriptionProfanityMasking, EnableTranscriptionTranslation, MaximumRecordingLength, etc. |
| `Get-CsCallingLineIdentity` | CallingIDSubstitute, EnableUserOverride, BlockIncomingPstnCallerID, ResourceAccount, CompanyName |
| `Get-CsTenantDialPlan` | SimpleName, NormalizationRules (collection) |
| `Get-CsOnlineVoiceRoutingPolicy` | OnlinePstnUsages (collection), RouteType |
| `Get-CsTeamsEmergencyCallingPolicy` | NotificationGroup, NotificationDialOutNumber, NotificationMode, EnhancedEmergencyServiceDisclaimer |
| `Get-CsTeamsEmergencyCallRoutingPolicy` | EmergencyNumbers (collection), AllowEnhancedEmergencyServices |

*Note: The voicemail policy cmdlet is `Get-CsOnlineVoicemailPolicy`, NOT `Get-CsTeamsVoicemailPolicy`. However, the Graph API uses `TeamsVoicemailPolicy` as the policyType value.

### CSV Schemas

#### 7.1 locations.csv

| Column | Type | PowerShell Source | Description |
|--------|------|-------------------|-------------|
| `locationId` | String (GUID) | `LocationId` from Get-CsOnlineLisLocation | Unique location identifier (used in number assignment) |
| `civicAddressId` | String (GUID) | `CivicAddressId` from Get-CsOnlineLisLocation | Parent civic address ID |
| `description` | String | `Location` or `Description` from Get-CsOnlineLisLocation | Location name/description |
| `companyName` | String | `CompanyName` from Get-CsOnlineLisCivicAddress | Company name |
| `address` | String | Computed: `HouseNumber` + `StreetName` + `StreetSuffix` | Full street address |
| `city` | String | `City` from Get-CsOnlineLisLocation | City name |
| `stateOrProvince` | String | `StateOrProvince` from Get-CsOnlineLisLocation | State/province code |
| `postalCode` | String | `PostalCode` from Get-CsOnlineLisLocation | Postal/ZIP code |
| `countryOrRegion` | String | `CountryOrRegion` from Get-CsOnlineLisLocation | Country code (e.g., "US") |
| `isDefault` | Boolean | Compare `LocationId` == `DefaultLocationId` from CivicAddress | Whether this is the default location |

**Example:**
```csv
locationId,civicAddressId,description,companyName,address,city,stateOrProvince,postalCode,countryOrRegion,isDefault
93cb8a70-b4af-41df-9928-d07607e21776,a1b2c3d4-...,Main Office - Floor 3,Contoso Ltd,123 Main St,Seattle,WA,98101,US,true
f47ac10b-58cc-4372-a567-0e02b2c3d479,a1b2c3d4-...,Branch Office,Contoso Ltd,456 Oak Ave,Portland,OR,97201,US,false
```

#### 7.2 policies.csv

| Column | Type | Source | Description |
|--------|------|--------|-------------|
| `policyType` | String | Computed | Policy type identifier (see below) |
| `policyName` | String | Identity | Policy name (what users see/use) |
| `policyId` | String | Computed | Policy ID for API calls (may equal policyName) |
| `description` | String | Description | Policy description |
| `isGlobal` | Boolean | Identity = "Global" | Whether this is the global/default policy |

**Policy Types (VERIFIED):**
| policyType Value | PowerShell Cmdlet | Graph policyType | Batch Assign |
|------------------|-------------------|------------------|--------------|
| `TeamsCallingPolicy` | Get-CsTeamsCallingPolicy | TeamsCallingPolicy | ✓ |
| `TeamsVoicemailPolicy` | Get-CsOnlineVoicemailPolicy* | TeamsVoicemailPolicy | ✗ |
| `CallingLineIdentity` | Get-CsCallingLineIdentity | CallingLineIdentity | ✓ |
| `TenantDialPlan` | Get-CsTenantDialPlan | TenantDialPlan | ✓ |
| `OnlineVoiceRoutingPolicy` | Get-CsOnlineVoiceRoutingPolicy | OnlineVoiceRoutingPolicy | ✓ |
| `TeamsEmergencyCallingPolicy` | Get-CsTeamsEmergencyCallingPolicy | TeamsEmergencyCallingPolicy | ✓ |
| `TeamsEmergencyCallRoutingPolicy` | Get-CsTeamsEmergencyCallRoutingPolicy | TeamsEmergencyCallRoutingPolicy | ✓ |

*Note: PowerShell cmdlet is `Get-CsOnlineVoicemailPolicy` but Graph API uses `TeamsVoicemailPolicy` as the type.

**Example:**
```csv
policyType,policyName,policyId,description,isGlobal
TeamsCallingPolicy,Global,Global,Default calling policy,true
TeamsCallingPolicy,AllowCalling,AllowCalling,Allows all calling features,false
TeamsCallingPolicy,RestrictedCalling,RestrictedCalling,Restricted calling for contractors,false
TeamsVoicemailPolicy,Global,Global,Default voicemail policy,true
TenantDialPlan,US-Seattle,US-Seattle,Seattle normalization rules,false
```

#### 7.3 export-metadata.json

```json
{
  "exportedAt": "2026-01-27T15:30:00Z",
  "exportedBy": "admin@contoso.com",
  "tenantId": "12345678-1234-1234-1234-123456789012",
  "tenantName": "Contoso Ltd",
  "scriptVersion": "1.0.0",
  "counts": {
    "locations": 15,
    "policies": 23
  }
}
```

### CLI Behavior

| Scenario | CLI Behavior |
|----------|--------------|
| CSVs present and fresh (<30 days) | Full validation, tab completion, location names in output |
| CSVs present but stale (>30 days) | Warning: "Reference data is X days old. Run Export-TeamsPhoneData.ps1 to refresh." |
| CSVs missing | Warning on first use: "Location/policy reference data not found. Commands will work but cannot validate IDs. Run Export-TeamsPhoneData.ps1 to enable validation." |
| locationId not in CSV | Warning: "Location ID not found in local cache. Proceeding anyway - ensure ID is valid." |

### PowerShell Authentication - VERIFIED

**Documentation:** [Application-based authentication in Teams PowerShell](https://learn.microsoft.com/en-us/microsoftteams/teams-powershell-application-authentication)

The MicrosoftTeams PowerShell module supports three authentication methods for unattended/service principal use:

| Method | Requirement | Security | Introduced |
|--------|-------------|----------|------------|
| **CertificateThumbprint** | Cert in Windows user cert store | High | v4.7.1-preview |
| **Certificate** (object) | Load cert from file/KeyVault | High | v4.9.2-preview |
| **AccessTokens** | Pre-obtained Graph + Teams tokens | Medium | v4.7.1-preview |

**Certificate Thumbprint (Windows only):**
```powershell
Connect-MicrosoftTeams `
  -CertificateThumbprint "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" `
  -ApplicationId "00000000-0000-0000-0000-000000000000" `
  -TenantId "YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY"
```
- Certificate must be installed in the **user certificate store** on Windows
- User running the script needs access to that cert store

**Certificate Object (Cross-platform):**
```powershell
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("C:\cert.pfx", $password)
Connect-MicrosoftTeams -Certificate $cert -ApplicationId $appId -TenantId $tenantId
```
- Certificate can be loaded from file or Azure KeyVault
- Works on Windows, Linux, macOS

**Access Tokens (Client Secret flow):**
```powershell
# Obtain tokens using client secret
$body = @{
  Grant_Type    = "client_credentials"
  Scope         = "https://graph.microsoft.com/.default"
  Client_Id     = $ApplicationId
  Client_Secret = $ClientSecret
}
$graphToken = (Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" -Method POST -Body $body).access_token

# Similar for Teams token with scope "48ac35b8-9aa8-4d74-927d-1f4a14a0b239/.default"
Connect-MicrosoftTeams -AccessTokens @("$graphToken", "$teamsToken")
```
- Less secure than certificates but easier to set up
- Works cross-platform

### Authorization Requirements - VERIFIED

**Important:** Uses **Entra ID roles**, NOT API permissions.

| Requirement | Details |
|-------------|---------|
| **App Registration** | Required in Microsoft Entra ID |
| **Role Assignment** | Assign **Teams Administrator** role to the service principal |
| **API Permissions** | Do NOT add "Skype and Teams Tenant Admin API" permission (causes failures) |
| **New Permissions (Sept 2025)** | `RoleManagement.Read.Directory` + `GroupMember.Read.All` required |

**Role Assignment Steps:**
1. Azure Portal → Microsoft Entra ID → Roles and administrators
2. Find "Teams Administrator" role
3. Add assignment → Select your app registration's service principal

### Cross-Platform Support - VERIFIED

| Platform | PowerShell Version | Status |
|----------|-------------------|--------|
| Windows | PowerShell 5.1 | **Recommended** - Best experience |
| Windows | PowerShell 7.2+ | Supported with known issues |
| Linux | PowerShell 7.2+ (`pwsh`) | Supported with known issues |
| macOS | PowerShell 7.2+ (`pwsh`) | Supported with known issues |

**Recommendation:** Use Windows PowerShell 5.1 for production scripts. PowerShell Core has known bugs with the Teams module.

### Unsupported Cmdlets (App Auth)

The following cmdlets do NOT work with application-based authentication:
- `New-Team`
- `Set-CsOnlineApplicationInstance`
- `*PolicyPackage*`
- `*-CsTeamsShiftsConnection*`
- `*-CsBatchTeamsDeployment*`

**Good news:** All `Get-Cs*` cmdlets for locations and policies ARE supported.

### Script Requirements

The export script (`Export-TeamsPhoneData.ps1`) must:

1. **Support service principal auth** - Certificate or access token method
2. **Accept parameters:**
   - `-TenantId` - Target tenant
   - `-ApplicationId` - App registration ID
   - `-CertificateThumbprint` (Windows) or `-CertificatePath` (cross-platform)
   - `-OutputPath` - Where to write CSVs (default: `~/.teams-phone/cache/`)
3. **Handle pagination** for large tenants
4. **Generate metadata.json** with export timestamp

**Example usage (Certificate Thumbprint - Windows):**
```powershell
./Export-TeamsPhoneData.ps1 `
  -TenantId "12345678-..." `
  -ApplicationId "87654321-..." `
  -CertificateThumbprint "ABC123..." `
  -OutputPath "~/.teams-phone/cache/"
```

**Example usage (Certificate File - Cross-platform):**
```powershell
./Export-TeamsPhoneData.ps1 `
  -TenantId "12345678-..." `
  -ApplicationId "87654321-..." `
  -CertificatePath "/path/to/cert.pfx" `
  -CertificatePassword (ConvertTo-SecureString "password" -AsPlainText -Force) `
  -OutputPath "~/.teams-phone/cache/"
```

### Future Enhancement

Post-MVP, if Microsoft adds Graph API endpoints for locations/policies, the CLI can:
1. Prefer real-time Graph calls
2. Fall back to CSV if Graph fails
3. Deprecate CSV approach

---

## Sources

- [List userConfigurations](https://learn.microsoft.com/en-us/graph/api/teamsadministration-teamsadminroot-list-userconfigurations?view=graph-rest-beta)
- [Get teamsUserConfiguration](https://learn.microsoft.com/en-us/graph/api/teamsadministration-teamsuserconfiguration-get?view=graph-rest-beta)
- [teamsUserConfiguration resource](https://learn.microsoft.com/en-us/graph/api/resources/teamsadministration-teamsuserconfiguration?view=graph-rest-beta)
- [List numberAssignments](https://learn.microsoft.com/en-us/graph/api/teamsadministration-telephonenumbermanagementroot-list-numberassignments?view=graph-rest-beta)
- [Get numberAssignment](https://learn.microsoft.com/en-us/graph/api/teamsadministration-numberassignment-get?view=graph-rest-beta)
- [numberAssignment resource](https://learn.microsoft.com/en-us/graph/api/resources/teamsadministration-numberassignment?view=graph-rest-beta)
- [numberAssignment: assignNumber](https://learn.microsoft.com/en-us/graph/api/teamsadministration-numberassignment-assignnumber?view=graph-rest-beta)
- [numberAssignment: unassignNumber](https://learn.microsoft.com/en-us/graph/api/teamsadministration-numberassignment-unassignnumber?view=graph-rest-beta)
- [numberAssignment: updateNumber](https://learn.microsoft.com/en-us/graph/api/teamsadministration-numberassignment-updatenumber?view=graph-rest-beta)
- [teamsPolicyUserAssignment: assign](https://learn.microsoft.com/en-us/graph/api/teamsadministration-teamspolicyuserassignment-assign?view=graph-rest-beta)
- [MC1169072 Announcement](https://m365admin.handsontek.net/microsoft-teams-microsoft-graph-apis-teams-administration-now-available-beta/)
- [Manage emergency locations](https://learn.microsoft.com/en-us/microsoftteams/add-change-remove-emergency-location-organization) (PowerShell only)
- [Get-CsOnlineLisCivicAddress](https://learn.microsoft.com/en-us/powershell/module/teams/get-csonlineliscivicaddress)
- [Get-CsOnlineLisLocation](https://learn.microsoft.com/en-us/powershell/module/teams/get-csonlinelislocation)
- [Get-CsTeamsCallingPolicy](https://learn.microsoft.com/en-us/powershell/module/teams/get-csteamscallingpolicy)
- [MicrosoftTeams PowerShell Module](https://learn.microsoft.com/en-us/microsoftteams/teams-powershell-overview)
- [telephoneNumberLongRunningOperation resource](https://learn.microsoft.com/en-us/graph/api/resources/teamsadministration-telephonenumberlongrunningoperation?view=graph-rest-beta)
- [Get telephoneNumberLongRunningOperation](https://learn.microsoft.com/en-us/graph/api/teamsadministration-telephonenumberlongrunningoperation-get?view=graph-rest-beta)
- [telephoneNumberManagementRoot resource](https://learn.microsoft.com/en-us/graph/api/resources/teamsadministration-telephonenumbermanagementroot?view=graph-rest-beta)
- [New-CsBatchPolicyAssignmentOperation (policy types)](https://learn.microsoft.com/en-us/powershell/module/microsoftteams/new-csbatchpolicyassignmentoperation?view=teams-ps)
- [effectivePolicyAssignment resource](https://learn.microsoft.com/en-us/graph/api/resources/teamsadministration-effectivepolicyassignment?view=graph-rest-beta)
- [Manage Voicemail Policies](https://learn.microsoft.com/en-us/microsoftteams/manage-voicemail-policies)
- [JSON Batching](https://learn.microsoft.com/en-us/graph/json-batching)
- [Batch Requests with SDKs](https://learn.microsoft.com/en-us/graph/sdks/batch-requests)
- [Application-based authentication in Teams PowerShell](https://learn.microsoft.com/en-us/microsoftteams/teams-powershell-application-authentication)
- [Connect-MicrosoftTeams cmdlet](https://learn.microsoft.com/en-us/powershell/module/microsoftteams/connect-microsoftteams?view=teams-ps)
- [Teams PowerShell Overview](https://learn.microsoft.com/en-us/microsoftteams/teams-powershell-overview)
- [Install Microsoft Teams PowerShell Module](https://learn.microsoft.com/en-us/microsoftteams/teams-powershell-install)
- [New-CsOnlineLisLocation](https://learn.microsoft.com/en-us/powershell/module/microsoftteams/new-csonlinelislocation?view=teams-ps)
- [New-CsOnlineLisCivicAddress](https://learn.microsoft.com/en-us/powershell/module/microsoftteams/new-csonlineliscivicaddress?view=teams-ps)
- [Get-CsOnlineVoicemailPolicy](https://learn.microsoft.com/en-us/powershell/module/microsoftteams/get-csonlinevoicemailpolicy?view=teams-ps)
- [Get-CsCallingLineIdentity](https://learn.microsoft.com/en-us/powershell/module/microsoftteams/get-cscallinglineidentity?view=teams-ps)
- [Get-CsTenantDialPlan](https://learn.microsoft.com/en-us/powershell/module/microsoftteams/get-cstenantdialplan?view=teams-ps)
- [Get-CsOnlineVoiceRoutingPolicy](https://learn.microsoft.com/en-us/powershell/module/teams/get-csonlinevoiceroutingpolicy?view=teams-ps)
- [Get-CsTeamsEmergencyCallingPolicy](https://learn.microsoft.com/en-us/powershell/module/teams/get-csteamsemergencycallingpolicy?view=teams-ps)
- [Get-CsTeamsEmergencyCallRoutingPolicy](https://learn.microsoft.com/en-us/powershell/module/microsoftteams/get-csteamsemergencycallroutingpolicy?view=teams-ps)
