Skip to content

Microsoft 365 Copilot

Copilot admin reporting: license adoption, usage, and license reclamation. Copilot has no reports endpoint of its own — it appears through the subscription, apps usage, and user sign-in data.

flowchart TD
    A[Graph] --> B["subscribedSkus — licenses"]
    A --> C["getM365AppUserCounts — usage CSV"]
    A --> D["users — license + sign-in activity"]
    B --> B1["Copilot SKU: consumed vs enabled"]
    C --> C1["Copilot column in the apps CSV"]
    D --> D1["Underused licenses"]

License adoption

Which Copilot SKUs are subscribed, and how many licenses are consumed vs enabled. Match on a --keyword (default COPILOT) against the SKU part number.

for sku in client.subscribed_skus.get().execute_query():
    if "COPILOT" in (sku.sku_part_number or "").upper():
        enabled = sku.prepaid_units.enabled if sku.prepaid_units else 0
        print(sku.sku_part_number, sku.consumed_units, "/", enabled)

Usage

Copilot activity from the Microsoft 365 apps usage report. When Copilot is licensed, getM365AppUserCounts gains a Copilot column; the script isolates it and prints active users per day. Without Copilot licenses there is no column, and the script says so.

rows = client.reports.get_m365_app_user_counts("D30").execute_query().csv_rows()
copilot_columns = [c for c in rows[0] if "copilot" in c.lower()]

Underused licenses

Users holding a Copilot license with no recent sign-in — candidates for license reclamation. Reading signInActivity needs AuditLog.Read.All; without it the script prints the hint and exits cleanly.

users = client.users.select(["userPrincipalName", "assignedLicenses", "signInActivity"]).get().execute_query()
licensed = [u for u in users if any(lic.skuId in sku_ids for lic in (u.assigned_licenses or []))]

Not yet covered by the library: Copilot admin settings and the AI-interaction audit log are exposed by Graph but have no SDK models yet.