# 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**

---

