Skip to content
Development
Skill

/licensing

The M365 subscription → SKU → service-plan model, seat availability versus consumption, assigning and removing licenses through Graph, the audit workflow for finding unused or misallocated seats, common SKU GUIDs, and licensing error causes.

From plugin
msp-claude-plugins
55200 skills165 agents200 commands4 MCP

Claude Code

$ npx -y skills add wyre-ai/msp-claude-plugins --skill licensing --agent claude-code

SKILL.md

licensing.SKILL.md
name: "Microsoft 365 Licensing"
description: >
  The M365 subscription → SKU → service-plan model, seat availability versus
  consumption, assigning and removing licenses through Graph, the audit workflow
  for finding unused or misallocated seats, common SKU GUIDs, and licensing error
  causes.
when_to_use: >-
  When checking, assigning, removing, or auditing Microsoft 365 licenses for a customer
  tenant. Use when: m365 license, microsoft 365 license,
  m365 seats, m365 sku, license audit, license usage m365, unused license m365, license assignment
  m365, m365 subscription, or license optimization.

Microsoft 365 Licensing

Conduit tools

The Conduit `m365` connector (hidden, personal-only) has no tools for this area. Through Conduit, reads go to the `microsoft-graph` connector as `microsoft_graph_get` (admin tier, read-only; use `microsoft_graph_suggest_queries` to build the query) and fleet operations go to the `cipp` connector:

  • Assignments and usage: `cipp_list_licenses`; CSP subscriptions: `cipp_list_csp_licenses`
  • SKU detail not in CIPP: `microsoft_graph_get` on `/v1.0/subscribedSkus`
  • Removing all of a leaver's licences: `cipp_offboard_user` with `RemoveLicenses: true` (admin tier). It removes every licence and the user's licence-assigning group memberships, and it only queues a job: check the user's licences afterwards before reporting success
  • Assigning or removing a SKU: `cipp_edit_user` (admin tier) with `licenses` set to the **complete** list of SKU IDs the user should hold; CIPP adds missing SKUs and removes any others, so include every SKU the user keeps
  • Changing service plans inside a SKU: no Conduit tool does this (`microsoft_graph_get` is read-only); do it in the Microsoft 365 admin center

Overview

M365 licensing is a top billing concern for MSPs. Licenses are purchased as SKU subscriptions, each containing bundles of service plans (Exchange, Teams, SharePoint, etc.). Efficient license management — finding unused seats, rightsizing SKUs, ensuring all users have what they need — directly impacts both the MSP's margin and the customer's costs.

Anti-triggers

Assigning a seat and buying a seat are different systems, and the word "license" covers both:

  • **Buying, cancelling, or repricing subscriptions** — changing what

the tenant *owns* happens at the distributor, not in Graph. `subscribedSkus` only reports what was already purchased. Use `pax8` (`pax8-subscriptions`), `sherweb`, or `cipp` for CSP licences.

  • **License cost, margin, or invoice reconciliation** — commercial data

lives with the distributor and in the PSA contract, not in Entra; use `pax8` (`pax8-invoices`) or the `finance-pack`.

  • **A licence audit across every customer tenant** — use the `cipp`

plugin (`cipp-licenses`).

  • **Reading seat utilisation without changing assignments** — the

vetted query catalogue answers this without composing a `$filter`; use the `microsoft-graph` plugin's `microsoft-graph-querying` skill.

Core Concepts

Subscription → SKU → Service Plans

M365 Business Premium (subscription)
  └── GUID: cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46
       ├── Exchange Online (service plan)
       ├── Microsoft Teams (service plan)
       ├── SharePoint Online (service plan)
       ├── Intune (service plan)
       └── Entra ID P1 (service plan)

License States

| State | Meaning | |-------|---------| | `Enabled` | Service plan is active and usable | | `Disabled` | Plan is turned off for this user (license still assigned) | | `Error` | Assignment failed — usually missing `usageLocation` | | `LockedOut` | Tenant billing issue | | `PendingInput` | Waiting for additional configuration |

Graph API Patterns

Get All SKUs Available in Tenant

GET /v1.0/subscribedSkus?$select=skuPartNumber,skuId,consumedUnits,prepaidUnits,servicePlans

**Response:**

{
  "value": [
    {
      "skuPartNumber": "SPE_E3",
      "skuId": "05e9a617-0261-4cee-bb44-138d3ef5d965",
      "consumedUnits": 42,
      "prepaidUnits": {
        "enabled": 50,
        "suspended": 0,
        "warning": 0
      },
      "servicePlans": [...]
    }
  ]
}

**Available seats = `prepaidUnits.enabled` - `consumedUnits`**

Get All Users With Their Assigned Licenses

GET /v1.0/users?$select=id,displayName,userPrincipalName,accountEnabled,assignedLicenses,usageLocation&$top=999

Find Users With a Specific License

Filter by SKU GUID:

GET /v1.0/users?$filter=assignedLicenses/any(x:x/skuId eq cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46)&$select=id,displayName,userPrincipalName,accountEnabled

Find Unlicensed Users

GET /v1.0/users?$filter=assignedLicenses/$count eq 0&$count=true&$select=id,displayName,userPrincipalName,accountEnabled

> Requires `ConsistencyLevel: eventual` header and `$count=true`

Assign a License to a User

POST /v1.0/users/{userId}/assignLicense
Content-Type: application/json

{
  "addLicenses": [
    {
      "skuId": "cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46",
      "disabledPlans": []
    }
  ],
  "removeLicenses": []
}

> `usageLocation` must be set on the user before assigning. Use `PATCH /v1.0/users/{id}` with `"usageLocation": "US"` first.

Remove a License from a User

POST /v1.0/users/{userId}/assignLicense
Content-Type: application/json

{
  "addLicenses": [],
  "removeLicenses": ["cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46"]
}

Disable Specific Service Plans (Partial License)

Assign a license but disable specific plans (e.g., give E3 without Teams):

POST /v1.0/users/{userId}/assignLicense
Content-Type: application/json

{
  "addLicenses": [
    {
      "skuId": "05e9a617-0261-4cee-bb44-138d3ef5d965",
      "disabledPlans": ["57ff2da0-773e-42df-b2af-ffb7a2317929"]
    }
  ],
  "removeLicenses": []
}

License Audit Workflow

Step 1: Inventory Available SKUs

Pull `subscribedSkus` and calculate:

  • Total purchased per SKU
  • Consumed seats
  • Ava
Read more
@wyre-technology
Ships withmsp-claude-plugins

One command to supercharge Claude Code for MSP workflows. Then restart Claude Code. That's it. Documentation: wyre-ai.github.io/msp-claude-plugins

55200 skills165 agents4 MCP
Get the whole plugin

Stars on GitHub

Momentum

stars in total
55
Star history of wyre-technology/msp-claude-plugins on GitHub, a chart by star-history.com

Other skills on msp-claude-plugins.

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills2w309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react5d252k
@facebook@facebookInstall Skill

Questions about licensing.

Which coding agents can use licensing?
licensing installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.