If you’ve ever had to juggle separate APIs for Outlook, SharePoint, Teams, and Azure AD just to build one integration, Microsoft Graph is the fix for that specific headache. One endpoint, one auth model, most of Microsoft 365 behind it. Here’s what it actually covers, how to authenticate against it properly, and where people usually get tripped up.
In this post: What is Microsoft Graph? · Sample requests · Getting an access token · v1.0 vs beta · Batching multiple requests · When would you use this? · Best practices · References
What is Microsoft Graph?
Microsoft Graph is a single REST API endpoint (https://graph.microsoft.com) for Outlook, SharePoint, Teams, Azure AD/Entra ID, Intune, and most other Microsoft 365 services. Instead of learning a separate API per service, you hit one endpoint and one auth model for all of it. That covers user/group management, email and calendar, files and collaboration, device/security policy, and audit/compliance data.
Sample requests
List users, via PowerShell:
# PowerShell script to get users from Microsoft Graph API
$accessToken = "YOUR_ACCESS_TOKEN"
$uri = "https://graph.microsoft.com/v1.0/users"
$response = Invoke-RestMethod -Uri $uri -Headers @{ Authorization = "Bearer $accessToken" } -Method Get
$response.value | Format-Table displayName, mail
Fetch a user’s email, via JavaScript:
const endpoint = "https://graph.microsoft.com/v1.0/me/messages";
fetch(endpoint, {
method: "GET",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
}
})
.then(response => response.json())
.then(data => console.log(data));
Getting an access token
Both examples above assume you already have $accessToken. In practice you get one by registering an app in Entra ID (App registrations), granting it the specific Graph permissions it needs, and choosing one of two permission models:
- Delegated permissions: the app acts as the signed-in user and can only do what that user could do themselves. Use this for anything running in a user’s context — a web app, an interactive script.
- Application permissions: the app acts as itself, independent of any user. Use this for background jobs, scheduled scripts, and service accounts. It needs admin consent, so scope it as narrowly as the task actually requires — an app that only needs to read a mailbox shouldn’t be granted tenant-wide user management.
Application permissions run unattended and need admin consent — scope them to exactly what the job needs, not “read everything” for convenience.
Use MSAL to acquire the token rather than hand-rolling OAuth, or use the Graph PowerShell SDK, which handles this for you:
Connect-MgGraph -Scopes "User.Read.All"
Get-MgUser -All | Select DisplayName, Mail
This is the more current recommended approach over raw Invoke-RestMethod calls for anything beyond a one-off script — token acquisition, refresh, and retry are handled for you.
v1.0 vs beta
Graph has a stable v1.0 endpoint and a beta endpoint carrying newer, less stable features. Default to v1.0 — beta endpoints can change shape or disappear without the same backward-compatibility guarantees, which is a bad surprise to find in a production integration.
Batching multiple requests into one call
A real gap in the “avoid throttling” advice above: if you need 10 unrelated pieces of data (a user’s profile, their manager, their calendar, three different group memberships), firing 10 separate requests is both slow and the fastest way to actually trigger throttling. Graph’s $batch endpoint bundles up to 20 independent requests into a single HTTP call:
POST https://graph.microsoft.com/v1.0/$batch
Content-Type: application/json
{
"requests": [
{ "id": "1", "method": "GET", "url": "/me" },
{ "id": "2", "method": "GET", "url": "/me/manager" },
{ "id": "3", "method": "GET", "url": "/me/calendarView?startDateTime=2026-07-01&endDateTime=2026-07-31" }
]
}
The response comes back as one JSON payload with each result tagged by its id, so you match them back up in code. Requests in the same batch can also depend on each other via dependsOn, running in the specified order rather than in parallel — useful when request 2 genuinely needs request 1 to succeed first. This is the actual fix for “my integration makes too many Graph calls and keeps getting throttled,” more often than tweaking retry logic after the fact.
When would you actually use this?
- You’re automating user provisioning/deprovisioning and don’t want to juggle separate Azure AD and Exchange APIs — Graph covers both from one endpoint.
- You need to pull a user’s email, calendar, and OneDrive files in a single integration — three separate legacy APIs collapse into one.
- You’re building a Power Automate flow or custom app that needs Microsoft 365 data and want a single auth/permission model to manage, not one per service.
Best practices
- Request least-privilege permissions — only what the app actually needs, not broad admin scopes by default.
- Handle throttling with backoff — Graph rate-limits, and retrying immediately just makes it worse.
- Use
$selectand$filterto pull only the fields you need instead of full objects every time. - Cache what doesn’t change often instead of re-querying the same data repeatedly.
- Use webhooks for real-time changes instead of polling on a timer.
References
That covers it for now. Questions welcome in the comments.
App Catalog Authentication Automation Backup Compliance Content Type CSS Flows Google Javascript Limitations List Metadata MFA Microsoft Node NodeJs O365 OneDrive Permissions PnP PnPJS Policy PowerApps Power Automate PowerAutomate PowerPlatform PowerShell React ReactJs Rest API Rest Endpoint Security Send an HTTP Request to SharePoint SharePoint SharePoint List SharePoint Modern SharePoint Online SPFX SPO Sync Tags Teams Termstore Versioning


