Article published on the 11th of November, 2026.
USE CASE · TECHNICAL EXAMPLE
Disclaimer
This article describes a possible use case for inspiration and a proof-of-concept implementation. It shows what WorkPoint can do together with Microsoft 365. It is not a prescribed architecture or a best-practice recommendation.
Before you use a similar approach in production, evaluate permissions, security, governance, licensing, error handling and operational requirements for the specific customer environment.
Introduction
Project meetings produce decisions, follow-up activities, responsibilities and schedule changes. Usually that information stays in Outlook and Teams, separate from the project it belongs to.
This use case shows how WorkPoint Automate, Microsoft Graph, Microsoft Teams and the Microsoft 365 Copilot Meeting AI Insights API can be combined so that:
- A user schedules a Teams meeting directly from a WorkPoint project.
- The meeting is registered in the project's Calendar list, together with the identifiers needed to find it again later.
- After the meeting, the AI-generated meeting notes are retrieved and stored on the project.
- AI-generated action items are converted into tasks on the project.
Solution overview
The solution connects WorkPoint with three Microsoft 365 services. All communication goes through Microsoft Graph, using HTTP endpoints from the WorkPoint HTTP Endpoint Library and an app-only token.
Figure 1. Components and Graph endpoints involved in the solution.
The solution consists of two independent WorkPoint processes. They are separate because AI insights only become available some time after the meeting has ended. The Calendar item holds all the information that the second process needs, so the two processes don't share any runtime state.
Figure 2. The two WorkPoint processes and the asynchronous gap between them.
Prerequisites
| Area | Requirement |
| Licensing | The meeting organizer, on whose behalf insights are retrieved, must have a Microsoft 365 Copilot license. |
| Teams policy | The organizer's Teams meeting policy must allow transcription (AllowTranscription). |
| Meeting | Transcription must actually be started during the meeting. |
| Entra ID | A dedicated app registration with application permissions and admin consent. |
| Teams | An application access policy that allows the app to access the organizer's meetings. |
| WorkPoint | A credential and HTTP endpoints in the HTTP Endpoint Library, plus two Automate processes. |
Authentication and permissions
Several independent layers must be configured before Graph returns meeting data. If any one is missing, calls fail with different, sometimes misleading errors.
Figure 3. Authentication and authorization layers.
Why a dedicated app registration
The built-in WorkPoint application (WorkPoint365.WebAPI) does not include calendar or meeting-insight permissions, and you can't extend it from the customer tenant. Requesting additional scopes on it results in consent errors (AADSTS65001).
This use case therefore uses a separate app registration, for example WorkPoint Meeting Integration. Its permissions are limited to this scenario and are managed independently of WorkPoint.
Graph application permissions
The credential authenticates with a client secret and no signed-in user. That means the permissions must be Application permissions. Delegated permissions are ignored in this flow. Grant admin consent after you add them.
| Permission (Application) | Used for |
| Calendars.ReadWrite | Creating the calendar event in the organizer's calendar |
| OnlineMeetings.Read.All | Looking up the onlineMeeting from the join URL |
| OnlineMeetingAiInsight.Read.All | Reading Meeting AI Insights |
Note
Application permissions apply across the tenant. For example, Calendars.ReadWrite lets the app write to any mailbox. In production, consider restricting mailbox access with Exchange application access controls (RBAC for Applications), and rotate the client secret regularly.
Teams application access policy
For online meeting and meeting insight APIs, Graph permissions alone are not enough. The app must also be allowed to act on behalf of the specific organizer through a Teams application access policy. Without one, Graph returns:
Forbidden – No application access policy found for this app on the user
Create and assign the policy with Teams PowerShell:
Install-Module MicrosoftTeams
Connect-MicrosoftTeams
New-CsApplicationAccessPolicy `
-Identity "WorkPointMeetingIntegration" `
-AppIds "application-client-id" `
-Description "Allow WorkPoint Meeting Integration to access Teams meetings"
Grant-CsApplicationAccessPolicy `
-PolicyName "WorkPointMeetingIntegration" `
-Identity "organizer-entra-object-id"
You can also assign the policy to a group of users or tenant-wide (-Global). A user can only have one application access policy at a time, and changes may take some time to take effect.
WorkPoint credential
In the HTTP Endpoint Library, create a credential of type Secret:
| Field | Value |
| Authority | https://login.microsoftonline.com/tenant-id (must include the tenant ID) |
| Audience | https://graph.microsoft.com |
| Client ID | Application (client) ID of the app registration |
| Tenant | Tenant ID |
| Scope | https://graph.microsoft.com/.default |
| Secret | The client secret value (not the secret ID) |
With .default, the token contains the application permissions that have been granted admin consent on the app registration.
Data model: the project Calendar list
The project Calendar is a custom SharePoint list with a calendar view, so you have full control over its columns. The Calendar item is the persistent link between the project and the meeting in Microsoft 365.
| Display name | Internal name | Type | Purpose |
| Title | Title | Text | Meeting subject |
| Start | Start | Date/Time | Start time |
| End | End | Date/Time | End time |
| Meeting Attendees | ccAttendees | Person (multi) | Organizer and attendees |
| Organizer | ccOrganizer | Person | Organizer |
| Organizer Id | ccOrganizerId | Text | Organizer's Entra object ID |
| Teams Meeting Url | ccTeamsMeetingUrl | Hyperlink | Join URL |
| Graph Event Id | ccGraphEventId | Multiple lines | Exchange event ID |
| Online Meeting Id | ccOnlineMeetingId | Multiple lines | Graph onlineMeeting ID |
| AI Status | ccAIStatus | Choice | Pending, Completed, Failed, NotAvailable |
| AI Meeting Summary | ccAISummary | Multiple lines | AI-generated meeting notes |
| AI Processed | ccAIProcessed | Date/Time | When the insight was retrieved |
The technical ID fields can be hidden from the standard views.
Tip:
Store all multi-line text fields as plain text. A rich-text column wraps the value in HTML (div class="ExternalClass…"), which makes it unusable in later API calls.
The identifiers are not interchangeable
Four different identifiers appear in the solution. Each one is obtained from the previous one:
Figure 4. The chain of identifiers from calendar event to AI insight.
| Identifier | Source | Used for |
| Graph Event ID (AAMk…) | Response from creating the event | Updating or cancelling the calendar event |
| Teams join URL | onlineMeeting.joinUrl on the event | Joining the meeting, and looking up the online meeting |
| Online Meeting ID (MSo…) | Online meeting lookup | Meeting AI Insights |
| AI Insight ID | List of AI insights | Getting the insight content |
The most common mistake is to use the Graph Event ID where the Online Meeting ID is expected.
Process 1: Schedule the meeting
Step 1 – Get the current user's ID
Look up the Entra object ID of the user who starts the process. That user becomes the meeting organizer, and the ID is used in the Graph URLs (/users/{organizerId}/…). The user also needs the application access policy described above.
Url:
https://graph.microsoft.com/v1.0/users/[upn]?$select=id,displayName,userPrincipalName
Note
This configuration uses a simple delegated credential (MSGraph) rather than the app-only credential used in the rest of the solution. Because the process always runs in the context of a signed-in user, the delegated credential can look up that user through /users/[upn] without granting the WorkPoint Meeting Integration app registration access to read user information across the tenant. All other Graph calls in the solution use the app-only credential.
Step 2 – Custom Form
The form collects Subject, Start, End and Attendees (people picker). Using separate start and end fields maps directly to the Graph payload and avoids extra duration logic.
Step 3 – Create the calendar event with a Teams meeting
For this http endpoint, we have created four parameters; subject, startTime, endTime, and attendees. We then set up a Post request endpoint as follows:
https://graph.microsoft.com/v1.0/users/[userId]/events
{
"subject": "[subject]",
"start": {
"dateTime": "[startTime]",
"timeZone": "Europe/Copenhagen"
},
"end": {
"dateTime": "[endTime]",
"timeZone": "Europe/Copenhagen"
},
"attendees": [attendees],
"isOnlineMeeting": true,
"onlineMeetingProvider": "teamsForBusiness"
}
We use a calendar-backed event (/events) rather than creating a standalone onlineMeeting. That way the meeting appears in Outlook, invitations are sent, and meeting artifacts are tied to a normal calendar meeting.
- Converting times. The form returns times in UTC. If you select 11:00 Danish time, the form returns 2026-11-10T10:00:00Z. If you only format the value and send it with timeZone: Europe/Copenhagen, the meeting is created one hour too early. Convert the value back to local time instead:
convertFromUTC(MeetingDetailsForm.startTime, "W. Europe Standard Time", "yyyy-MM-ddTHH:mm:ss")
convertFromUTC(MeetingDetailsForm.endTime, "W. Europe Standard Time", "yyyy-MM-ddTHH:mm:ss")
Don't use addHours(…, 1). It breaks when daylight saving time changes.
- Transforming attendees. The people picker returns SharePoint user objects. Graph expects an array of attendee objects:
select(
MeetingDetailsForm.attendees,
x,
{
"emailAddress": { "address": x.UserPrincipalName, "name": x.LookupValue },
"type": "required"
}
)
In the Post body, the attendees parameter must be inserted without quotes: "attendees": [attendees]. With quotes, the array is sent as a string, and Graph responds with Property attendees in payload has a value that does not match schema.
All attendees are created as required, because the form doesn't distinguish between required and optional attendees.
Response. From the response, keep id (becomes ccGraphEventId) and onlineMeeting.joinUrl (becomes ccTeamsMeetingUrl). Note that the response does not contain the Online Meeting ID, which we will need later.
Step 4 – Resolve the Online Meeting ID
To resolve and retrieve the Teams meeting's online meeting Id, we use the following http Get request:
Url:
https://graph.microsoft.com/v1.0/users/[organizerId]/onlineMeetings?$filter=JoinWebUrl eq '[teamsMeetingUrl]'
The response is a collection. value[0].id is the Online Meeting ID, which we store in ccOnlineMeetingId in the next step.
Step 5 – Create the Calendar item
| Field | Value |
| Title | CreateMeeting.subject |
| Start | CreateMeeting.start.dateTime |
| End | CreateMeeting.end.dateTime |
| AI Status | Pending |
| Organizer (ccOrganizer) | CurrentUser |
| Organizer Id (ccOrganizerId) | GetUser.id |
| Attendees (ccAttendees) | union(createArray(CurrentUser), MeetingDetailsForm.attendees) |
| Graph Event Id (ccGraphEventId) | CreateMeeting.id |
| Teams Meeting Url (ccTeamsMeetingUrl) | CreateMeeting.onlineMeeting.joinUrl |
| Online Meeting Id(ccOnlineMeetingId) | GetMeetingId.value[0].id |
createArray() is required because CurrentUser is a single object and union() expects two arrays. The organizer is included in the Calendar item's attendees, but not in the Graph invitation.
Summary
Running the process, the first step the user encounters is the Meeting details form:
After hitting Continue, the process creates the Teams meeting for all attendees:
The process then gets the meeting's ID and creates the meeting inside the Project's Calendar list:
Opening the Calendar item reveals the meeting's Join Url, which the user can use to join the meeting (or they can join from their own Teams/Outlook client):
Process 2: Retrieve the AI summary and create tasks
Step 1 – Select the meeting
An Item Search Form lets the user select the Calendar item. The selected item contains ccOrganizerId, ccOnlineMeetingId and ccAttendees, which is everything the rest of the process needs.
Step 2 – List AI insights
To get the selected meeting's AI insights item, we use a Get http request using two parameters; organizerId and onlineMeetingId:
Url:
https://graph.microsoft.com/v1.0/copilot/users/[organizerId]/onlineMeetings/[onlineMeetingId]/aiInsights
This is a list call. It returns metadata only, such as id, callId and createdDateTime, not the content. An empty response is expected for a while after the meeting:
{"@odata.count": 0, "value": []}
That usually means not ready yet, not an error. In testing, the AI summary was visible in Teams Recap several minutes before it was available through the API. When there is content, value[0].id is the AI Insight ID.
Figure 5. Handling the delay before AI insights become available.
The demo process doesn't implement retries and assumes the insight is already available.
Step 3 – Get the AI insight content
To get the actual contents of the AI insight for the meeting, we create another http endpoint using three parameters; userId, onlineMeetingId, and aiInsightId:
Url:
https://graph.microsoft.com/v1.0/copilot/users/[userId]/onlineMeetings/[onlineMeetingId]/aiInsights/[aiInsightId]
Example response (shortened):
{
"meetingNotes": [
{
"title": "Lobby Material Selection And Project Schedule",
"text": "The team outlined the lobby's material requirements…",
"subpoints": [
{
"title": "Wall Panels",
"text": "The customer requested wooden acoustic panels…"
},
{
"title": "Schedule",
"text": "Materials will be sourced in early January…"
}
]
}
],
"actionItems": [
{
"title": "Material Suppliers",
"text": "Contact the usual suppliers to check whether they can provide the lobby materials.",
"ownerDisplayName": "John Doe"
}
]
}
- meetingNotes contains topics, each with a title, a text and subpoints.
- actionItems contains a title, a text and ownerDisplayName.
Step 4 – Update the Calendar item
In this step we update the Calendar item selected in the first form with the AI summary coming from the AI insights, as well as the AI status:
| Field | Value |
| ccAISummary | GetAIMeetingSummaryContent.meetingNotes[0].text |
| ccAIStatus | Completed |
| ccAIProcessed | utcNow() |
Step 5 – Create tasks from action items
In the final step, we take the action item produced by AI in the AI insights of the meeting and turn them into tasks on our project:
The Step Input on the Create documents, folders and items step is set using the following expression:
select(
GetAIMeetingSummaryContent.actionItems,
x,
{
'Title': x.title,
'Body': x.text,
'AssignedTo': select(
where(
ItemSearchForm.ccAttendees,
attendee,
contains(toLower(attendee.LookupValue), toLower(x.ownerDisplayName))
),
attendee,
{ 'LookupValue': attendee.LookupValue, 'LookupId': attendee.LookupId }
)
}
)
Summary
Running the process, the first form the user encounters allows them to select the meeting they want to extract AI summary information from:
After selecting a meeting and hitting Continue, the process accesses and extracts information from the AI insights for the meeting. It then updates the Calendar item inside the project with the AI summary:
The process then takes the action items from the AI insights and creates project tasks from them:
Limitation: assigning action-item owners
ownerDisplayName is only an AI-generated text string. The actionItem schema has no user ID, UPN or email address. That means the owner can't be mapped reliably to a SharePoint person.
The expression above tries to match the name against the meeting's attendees. This narrows the search a lot compared with searching the whole tenant, but it is still best effort:
- Exact matching often misses. For example, the AI returned WorkPoint while the user is named WorkPoint Demo Administrator.
- Substring matching (contains) can hit the wrong person or several people. With Peter, both Peter Jensen and Peter Hansen would match.
A more defensive approach is shown below:
Figure 6. Defensive owner matching against the meeting's attendees.
- Try an exact match first, then a substring match.
- Assign automatically only if exactly one attendee matches.
- Otherwise, create the task without AssignedTo.
- Always store the AI-suggested name in a text field (for example ccAIOwnerDisplayName), so the information isn't lost.
In general, treat AI output as a suggestion, not as authoritative business data.
Troubleshooting
| Symptom | Cause | Solution |
| attendees … does not match schema | The array is sent as a string | Remove the quotes around the parameter |
| ErrorAccessDenied … Calendars.ReadWrite… | The token is missing calendar permissions | Add the application permission and grant admin consent |
| AADSTS65001 | A scope is requested on an app without consent | Use a separate app registration |
| Authority URI should have at least one segment | The authority is missing the tenant ID | https://login.microsoftonline.com/tenant-id |
| No application access policy found | The Teams policy is missing | Create the policy and assign it to the organizer |
| Connect-MicrosoftTeams not recognized | The module isn't installed | Install-Module MicrosoftTeams |
| A window handle must be configured | Sign-in fails in PowerShell ISE | Use a regular PowerShell console |
| The meeting is created 1 hour early | A UTC value is sent as local time | Use convertFromUTC |
| To get permission to start transcription, talk to your admin | The meeting policy has AllowTranscription = False | Set-CsTeamsMeetingPolicy -AllowTranscription $true on the relevant policy |
| aiInsights returns value: [] | The insight hasn't been generated yet, or there's no transcript | Wait and try again, and check the transcript in Recap |
| AssignedTo is empty | ownerDisplayName doesn't match any attendee | Expected behavior. See the limitation above. |
Considerations for production
- Automatic post-processing. Instead of manual selection, a scheduled process could pick up items where End now and ccAIStatus = Pending, with a limited number of retries before setting the status to NotAvailable.
- Least privilege. Restrict the app's mailbox access, and assign the application access policy to a group rather than the whole tenant.
- Secret management. Plan for client secret expiry and rotation, or use a certificate.
- Meeting changes. Moving or cancelling meetings in Outlook isn't synchronized back to the Calendar item.
- Data classification. Meeting summaries can contain sensitive information and inherit the project site's permissions.
- Licensing. Meeting AI Insights require Microsoft 365 Copilot for the organizer. Meetings organized by users without a license won't get insights.
Summary
This use case shows that WorkPoint can tie the full meeting lifecycle to the project: scheduling, invitation, transcription, the AI summary and follow-up tasks. The key technical points are:
- The Calendar item stores the chain of identifiers: Event ID → join URL → Online Meeting ID → AI Insight ID.
- App-only access requires both Graph application permissions and a Teams application access policy.
- AI insights are generated asynchronously, so an empty response doesn't mean failure.
- AI-generated action-item owners are text, not identities, so assignment must be best effort.
Comments
0 comments
Please sign in to leave a comment.