From m365
Provides Microsoft Graph API patterns including authentication, OData query operators, pagination, throttling/retry, batch requests, and delta queries. Use for constructing M365 API calls or debugging errors.
npx claudepluginhub wyre-technology/msp-claude-plugins --plugin m365This skill uses the workspace's default tool permissions.
Microsoft Graph is the unified REST API surface for all M365 services (users, mail, calendar, Teams, OneDrive, security). All M365 MCP tools ultimately make Graph calls. Understanding Graph's OData query syntax, pagination model, throttling behavior, and auth patterns is essential for building reliable M365 workflows.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Designs, implements, and audits WCAG 2.2 AA accessible UIs for Web (ARIA/HTML5), iOS (SwiftUI traits), and Android (Compose semantics). Audits code for compliance gaps.
Microsoft Graph is the unified REST API surface for all M365 services (users, mail, calendar, Teams, OneDrive, security). All M365 MCP tools ultimately make Graph calls. Understanding Graph's OData query syntax, pagination model, throttling behavior, and auth patterns is essential for building reliable M365 workflows.
The Softeria MS-365 MCP server (--org-mode) accepts a Bearer token per request via the Authorization header. The token must be obtained from Microsoft Entra using the common endpoint (multi-tenant) with the following scopes:
| Scope | Purpose |
|---|---|
https://graph.microsoft.com/.default | All Graph permissions granted to app |
openid | ID token for tenant ID extraction |
profile | User profile claims |
offline_access | Refresh token for session persistence |
All Graph API calls use:
https://graph.microsoft.com/v1.0/
Beta features (preview, subject to change):
https://graph.microsoft.com/beta/
| Parameter | Purpose | Example |
|---|---|---|
$select | Return only specified fields | $select=id,displayName,email |
$filter | Filter results | $filter=accountEnabled eq true |
$search | Full-text search (mailboxes, users) | $search="displayName:Smith" |
$orderby | Sort results | $orderby=displayName asc |
$top | Limit results per page | $top=100 |
$skip | Skip N results | $skip=100 |
$count | Include total count | $count=true |
$expand | Include related entities | $expand=manager |
eq equal $filter=accountEnabled eq true
ne not equal $filter=jobTitle ne 'Manager'
gt/lt greater/less $filter=createdDateTime gt 2024-01-01T00:00:00Z
ge/le >=, <= same but inclusive
and/or logical $filter=dept eq 'IT' and accountEnabled eq true
not negation $filter=not startsWith(mail,'test')
startsWith prefix match $filter=startsWith(displayName,'J')
endsWith suffix match $filter=endsWith(mail,'@contoso.com')
contains substring $filter=contains(jobTitle,'Manager')
any/all collection ops $filter=assignedLicenses/any(x:x/skuId eq '<guid>')
ConsistencyLevel: eventual)Some filters require the ConsistencyLevel: eventual header and $count=true:
GET /v1.0/users?$filter=assignedLicenses/$count eq 0&$count=true&ConsistencyLevel: eventual
Graph uses server-side pagination. Always check for @odata.nextLink in responses.
1. Make initial request with $top=100
2. If response contains @odata.nextLink → follow it
3. Repeat until no @odata.nextLink
Response with more pages:
{
"@odata.context": "...",
"@odata.nextLink": "https://graph.microsoft.com/v1.0/users?$skiptoken=abc123",
"value": [...]
}
Response — last page:
{
"@odata.context": "...",
"value": [...]
}
The Softeria MCP server handles pagination automatically for supported operations.
Delta queries return only changed items since the last sync — ideal for regular polling.
GET /v1.0/users/delta?$select=id,displayName,userPrincipalName,accountEnabled
Response includes a @odata.deltaLink after the last page — store this.
GET {deltaLink}
Returns only users added, modified, or deleted since the delta token was issued. Deleted users have "@removed": { "reason": "deleted" } in the response.
Graph throttles requests at both per-app and per-tenant levels. Throttled responses return 429 Too Many Requests.
Always check the Retry-After header on 429:
HTTP/1.1 429 Too Many Requests
Retry-After: 15
Wait the specified seconds before retrying.
| Resource | Limit |
|---|---|
| Per app per tenant | 10,000 req/10 min |
| Per user | 3,000 req/10 min |
| Mail search | 10 concurrent |
| Reports | 1 req/15 min per report |
$select to request only needed fields (reduces payload + throttle pressure)$filter server-side rather than fetching all and filtering in codeCombine up to 20 independent Graph calls into one HTTP request:
POST /v1.0/$batch
Content-Type: application/json
{
"requests": [
{
"id": "1",
"method": "GET",
"url": "/users/user1@contoso.com?$select=displayName,accountEnabled"
},
{
"id": "2",
"method": "GET",
"url": "/users/user2@contoso.com?$select=displayName,accountEnabled"
}
]
}
Response:
{
"responses": [
{ "id": "1", "status": 200, "body": { "displayName": "Alice", "accountEnabled": true } },
{ "id": "2", "status": 200, "body": { "displayName": "Bob", "accountEnabled": false } }
]
}
Use for fetching details on multiple known user IDs simultaneously.
| HTTP | Code | Meaning | Fix |
|---|---|---|---|
| 400 | Request_BadRequest | Malformed OData filter | Check filter syntax |
| 401 | InvalidAuthenticationToken | Token expired or malformed | Re-authenticate |
| 403 | Authorization_RequestDenied | Missing Graph permission | Add scope, re-consent |
| 404 | Request_ResourceNotFound | Object doesn't exist | Check GUID/UPN |
| 429 | ActivityLimitReached | Throttled | Wait Retry-After seconds |
| 500 | ServiceInternalServerError | Graph outage | Retry with backoff |
| 503 | ServiceUnavailable | Transient | Retry with backoff |
For testing queries interactively: https://developer.microsoft.com/graph/graph-explorer
Log in with a test M365 account to explore API responses before writing code.