<ANGLE_BRACKET> values are the only things you change.
What you are creating
1. The mailbox
Create a normal user with a licence that includes Exchange Online and Microsoft Teams (Business Basic is enough). Nothing about it is special except its name.2. The app registration
3. The four application permissions
These are application permissions (app-only, no signed-in user), not delegated ones.
The role ids are the same in every tenant, but you never have to trust that — ask Graph:
az ad app permission grant is not the same thing and is not enough here: it records a
delegated grant, while application roles require admin-consent. If a later call returns
Access is denied. Check credentials and try again., consent is the first thing to re-check.4. Scope the app to one mailbox — Exchange ApplicationAccessPolicy
Create a mail-enabled security group containing only the Minutes mailbox, then bind the app to it. Everything outside the group becomes invisible to that credential.Denied that later becomes Granted means
you tested too early.
5. Let the app create Teams meetings
Teams gates application-permission meeting creation separately, with its own policy:Forbidden even though every permission is consented — a genuinely confusing state, so
check the clock before you check your work.
6. The secret
invalid_client with no warning window.
7. Prove it works
Four checks, in order. Each one fails distinctly, which is the point of doing them separately.DELETE /v1.0/users/$MAILBOX_ID/onlineMeetings/<id>.
Gotchas, in the order they bite
onlineMeetings needs the objectId GUID — the UPN gives a misleading error
onlineMeetings needs the objectId GUID — the UPN gives a misleading error
POST /users/minutes@your-domain/onlineMeetings fails with a message about the value not
being a valid GUID or the segment not being found. It reads like a malformed request; it is
just the wrong identifier. /users/{objectId} is required on this endpoint even though the
UPN works fine on /users/{upn}/messages. Keep the GUID from step 1 next to the UPN.An `az login` is not the app credential
An `az login` is not the app credential
Testing while signed in as yourself proves your own admin rights, not the app’s. It will
happily read every mailbox and create every meeting, and tell you nothing about whether the
ApplicationAccessPolicy works. Always test with a client-credentials token as in step 7.
Admin consent is a separate act from adding the permission
Admin consent is a separate act from adding the permission
az ad app permission add records a request. Until admin-consent runs, the token comes
back without the roles claim and every call returns Access is denied. Decode the
token and look at roles before debugging anything else.The Teams policy is not the Exchange policy
The Teams policy is not the Exchange policy
They are different systems with similarly named cmdlets. Mail works and meetings return
Forbidden → the Teams New-/Grant-CsApplicationAccessPolicy step is missing or has not
propagated yet (up to 30 minutes).Inviting the mailbox from the mailbox's own account delivers nothing
Inviting the mailbox from the mailbox's own account delivers nothing
Exchange suppresses self-delivery: if the Minutes mailbox is both organiser and attendee, the
event is written straight to its calendar and no invitation is delivered. When you test
the flow end to end, invite the Minutes mailbox from a different account. An empty inbox
here is Exchange behaving as designed, not Vexa failing to receive.
Secrets expire silently
Secrets expire silently
az ad app credential reset defaults to a short lifetime unless you pass --years. Expiry
surfaces as invalid_client with no prior warning. Put the date in a calendar.What Vexa can and cannot see
To revoke everything at once, in one command: