MSP all tenants in one question
21 Oct 2026
Which client has the most licence waste? One question for an MSP
Seven clients, seven admin centres, one question. The Microsoft 365 usage report that answers "who hasn't used their licence" is a per-tenant CSV: one download per client, one spreadsheet to stitch them, and by the time the last tab is open the first one is a day old. GDAP gets a partner into each client's admin portals, partitioned per customer. The comparison between clients is still the spreadsheet.
This is the playbook for doing it as one question with the GraphPaaS connector for Claude. Every call below was made on our demo organisation, which holds two synthetic tenants, Fabrikam HQ and Fabrikam Iberia. Two clients instead of seven, but the calls don't change with the count, and neither does the answer's shape: one row per client.
Six steps from "which client" to a table
The connector won't guess. With more than one tenant readable and no
tenant_id, it refuses and hands Claude the list, so Claude asks you to
pick: one option per client, plus "All tenants". A silent default would answer about a
client you never meant. Done when the options Claude offers match your client
list.
Which client has the most licence waste?
get_metrics(domain="licence_waste", group_by="tenant") → 400: you can read 2 tenants — ask the user which one they mean (one option per tenant plus 'All tenants' (tenant_id="all"))
You can read two tenants: Fabrikam HQ (Demo) and Fabrikam Iberia (Demo). Which one, or all tenants?
The licence_waste domain holds every user with a paid
licence, not only the wasted ones, so a group_by with no filter is the
denominator. In the demo: 69 rows, 42 in HQ and 27 in Iberia. Each tenant also carries
its own collected_at (HQ on 18 October at 05:45 UTC, Iberia on 17
October at 17:57 UTC), because Microsoft publishes this report once a day per tenant.
Done when you have one count per client and know how old each one is.
A licence is wasted when its last activity is older than 90 days or
missing altogether: Microsoft drops the date for people who were never active, or not
for a long time. where ANDs its conditions, so "null or stale" is two
questions. Done when both counts are in, per client.
All tenants. Which client has the most licence waste?
get_metrics(domain="licence_waste", tenant_id="all", group_by="tenant")
→ Fabrikam HQ (Demo) 42 · Fabrikam Iberia (Demo) 27
get_metrics(domain="licence_waste", tenant_id="all",
where={"last_activity": null}, group_by="tenant")
→ Fabrikam HQ (Demo) 5 · Fabrikam Iberia (Demo) 3
get_metrics(domain="licence_waste", tenant_id="all",
where={"last_activity": {"<": "2026-07-20"}}, group_by="tenant")
→ Fabrikam HQ (Demo) 5 · Fabrikam Iberia (Demo) 3
Fabrikam HQ: 10 of its 42 licensed users (24%) show no activity in 90 days, five with no date at all. Fabrikam Iberia: 6 of 27 (22%). HQ leads on both the count and the rate, but only just on the rate.
A count always favours your biggest client. The rate says who manages
licences worse; the count says where a renewal call saves the most. Here they agree.
Across seven clients of different sizes they usually won't, and the list of names
behind each count is one more call with columns. Our
licence review in
one prompt walks that per-client follow-up. Done when each client has a
rate and a named list.
The mfa_registration domain comes from Microsoft's
userRegistrationDetails,
where isMfaRegistered means a strong method is registered, even one the
authentication methods policy doesn't allow, and isAdmin flags anyone in an
admin role. The overall rate is the slide for the client; the admins are the ticket.
Done when every admin without MFA has a name next to a client.
And MFA coverage per client? Any admins without it?
get_metrics(domain="mfa_registration", tenant_id="all",
where={"mfa_registered": false}, group_by="tenant")
→ Fabrikam HQ (Demo) 7 · Fabrikam Iberia (Demo) 5
get_metrics(domain="mfa_registration", tenant_id="all",
where={"mfa_registered": false, "is_admin": true},
columns=["tenant", "display_name", "user_type", "methods"])
→ 4 rows
HQ has 35 of 42 users registered (83%), Iberia 22 of 27 (81%). Each has two admins with no method registered at all: Ana Dias and Diego Yilmaz, members in both tenants.
Same names in both tenants is the demo showing its seams: it reuses the same synthetic people in both tenants. In a real portfolio, the same name across two clients is worth a second look of its own: a contractor, or one of your own technicians with a local account in each.
| Client (demo) | Licensed users | Idle 90 days | Waste rate | MFA registered | Admins without MFA |
|---|---|---|---|---|---|
| Fabrikam HQ | 42 | 10 (5 with no date) | 24% | 35 of 42 (83%) | 2 |
| Fabrikam Iberia | 27 | 6 (3 with no date) | 22% | 22 of 27 (81%) | 2 |
Five calls, one table. With seven clients it is still five calls; only the table grows.
"All" never means the whole organisation. It means the tenants the
caller can read. A member restricted to three clients gets those three in the
dashboard's all-tenants views and in Claude, and nothing else; an API key limited to
them gets the same three. It's the per-customer partition GDAP brought to partner
access, applied to the reading. Done when a restricted technician's
list_tenants shows only their clients.
Why the demo tenants stay out of your sums
Every organisation can add demo tenants, to show a client what a report looks like
without opening a real one. They are synthetic, and a sum that adds them to real clients
is a wrong number with a straight face. So the rule is in the connector: once an
organisation has one real tenant, tenant_id="all" leaves the demo tenants
out. Name one explicitly and it still answers. Our demo organisation has no real tenant,
which is the only reason both Fabrikam tenants show up in the calls above.
Tenants that can't answer don't vanish either. A client without the licence or the
permission for a domain is listed under unavailable with the reason, so
Claude says which client can't answer instead of ranking it as the cleanest one.
The same holds for MFA gaps: our
MFA registration gaps
article covers what the registration report doesn't see.
tenant_id="all" plus group_by="tenant" turns any per-tenant
question into a per-client ranking: waste, MFA, guests, stale devices. Ask for the
rate, not only the count, check each tenant's collected_at, and read the
unavailable list before you call any client clean.
- reportRoot: getOffice365ActiveUserDetail — the per-tenant active users CSV, with last activity per workload and assigned products.
- userRegistrationDetails resource type — isMfaRegistered vs isMfaCapable, and the isAdmin flag for privileged accounts.
- Introduction to granular delegated admin privileges (GDAP) — least-privileged partner access, partitioned per customer.
- Workloads supported by GDAP — Lighthouse's multitenant view follows each user's delegated permissions.
Ask every client tenant the same question, and get one row per client back.
Add the connector →