
Msgraph
- 971 installs
- 102 repo stars
- Updated August 4, 2026
- merill/msgraph
msgraph is a Claude agent skill with a searchable library of community-contributed Microsoft Graph API query samples that helps developers and coding agents call the correct Graph endpoints without hallucinating outdated
About
msgraph is a Microsoft Graph integration skill that solves a common agent failure mode: large language models trained on months-old data hallucinate wrong Graph API URLs, scopes, and query parameters. The skill maintains a community-contributed search index of working Graph query samples that agents query to find the exact REST call for Entra ID, Microsoft 365, Teams, SharePoint, and other Graph resources. Developers reach for msgraph when building Microsoft 365 integrations, enterprise SSO features, or admin automation and need agents to emit valid Graph requests on the first attempt. Contributors can add new query samples directly into the skill index via graph.pm contribution workflows.
- Community-contributed query samples library
- Maps natural language intents directly to correct Graph API calls
- Supports multi-step query sequences with variable substitution
- Organized by product areas including Entra ID and Exchange
- Continuously updated samples keep agents current with latest Graph changes
Msgraph by the numbers
- 971 all-time installs (skills.sh)
- +37 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #406 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/merill/msgraph --skill msgraphAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 971 |
|---|---|
| repo stars | ★ 102 |
| Security audit | 1 / 3 scanners passed |
| Last updated | August 4, 2026 |
| Repository | merill/msgraph ↗ |
How do you stop agents hallucinating Microsoft Graph API calls?
Let their AI coding agent discover and call the exact Microsoft Graph API endpoints it needs without hallucinating outdated URLs or parameters.
Who is it for?
Developers building Microsoft 365, Entra ID, Teams, or SharePoint integrations who need coding agents to reference current Graph API samples instead of stale training data.
Skip if: Projects with no Microsoft ecosystem dependency or teams that already maintain a private, up-to-date Graph SDK wrapper with full test coverage.
When should I use this skill?
User asks to call Microsoft Graph, integrate with Microsoft 365, query Entra ID, or automate Teams or SharePoint via Graph REST APIs.
What you get
Verified Graph API query samples, correct endpoint URLs, OAuth scopes, and request bodies ready for agent-generated integration code.
- Graph API query samples
- verified REST request templates
Files
Microsoft Graph Agent Skill
Search, look up, and call any of the 27,700+ Microsoft Graph APIs — all locally, no network calls needed. Use the three search commands to find the right endpoint, check permissions and parameters, then optionally execute calls directly or hand off to a Graph MCP server.
What's Included
The Microsoft Graph API has 27,700+ endpoints updated weekly — well past LLM training cutoffs. This skill bundles the complete API surface as local indexes that you search instantly with no network calls.
| Index | Count | What it contains |
|---|---|---|
| OpenAPI endpoints | 27,700+ | Path, method, summary, description, permission scopes |
| Endpoint docs | 6,200+ | Permissions (delegated/app), query parameters, required headers, default vs $select-only properties |
| Resource schemas | 4,200+ | All properties with types, supported $filter operators, default/select-only flags |
| Community samples | Growing | Hand-verified queries mapping natural-language tasks to exact API calls |
How to Run
The msgraph CLI is bundled with this skill. Run all commands through the launcher script in this skill's directory:
- macOS / Linux:
bash <path-to-this-skill>/scripts/run.sh <command> [args...] - Windows:
powershell <path-to-this-skill>/scripts/run.ps1 <command> [args...]
For example, to search for mail-related APIs on macOS:
bash /home/user/.opencode/skills/msgraph/scripts/run.sh openapi-search --query "send mail"In all examples below, msgraph is shorthand for the full launcher invocation.
Finding the Right API
This is the primary purpose of the skill. Follow this progressive lookup strategy — each level adds detail:
1. Your own knowledge — try first for well-known endpoints (/me, /users, /groups). 2. `sample-search` — curated, hand-verified samples. Highest quality. Use for common tasks and multi-step workflows. 3. `api-docs-search` — per-endpoint permissions, supported query parameters, required headers, default vs $select-only properties, and resource property details with filter operators. 4. `openapi-search` — full catalog of 27,700 Graph APIs. Use when you cannot find the endpoint any other way. 5. Reference files — concept docs on query parameters, advanced queries, paging, batching, throttling, errors, and best practices. Read only when you need specific guidance.
This order is guidance — adapt based on the task. For example, jump straight to api-docs-search if you already know the endpoint but need its permissions.
sample-search
Search curated community samples that map natural-language tasks to exact Microsoft Graph API queries:
msgraph sample-search --query "conditional access policies"
msgraph sample-search --product entra
msgraph sample-search --query "managed devices" --product intune| Flag | Description |
|---|---|
--query | Free-text search (searches intent and query fields) |
--product | Filter by product: entra, intune, exchange, teams, sharepoint, security, general |
--limit | Max results (default 10) |
At least one of --query or --product is required. Results include multi-step workflows.
api-docs-search
Look up detailed documentation for a specific endpoint or resource type:
msgraph api-docs-search --endpoint /users --method GET
msgraph api-docs-search --resource user
msgraph api-docs-search --query "ConsistencyLevel"| Flag | Description |
|---|---|
--endpoint | Search by endpoint path (e.g. /users, /me/messages) |
--resource | Search by resource type name (e.g. user, group, message) |
--method | Filter by HTTP method: GET, POST, PUT, PATCH |
--query | Free-text search across all fields |
--limit | Max results (default 10) |
At least one of --endpoint, --resource, or --query is required.
Endpoint results include: required permissions (delegated work/school, delegated personal, application), supported OData query parameters, required headers, default properties, and endpoint-specific notes.
Resource results include: all properties with types, supported $filter operators (eq, ne, startsWith, etc.), and whether each property is returned by default or requires $select.
openapi-search
Search the full OpenAPI catalog of 27,700 Microsoft Graph APIs:
msgraph openapi-search --query "send mail"
msgraph openapi-search --resource messages --method GET| Flag | Description |
|---|---|
--query | Free-text search (searches path, summary, description) |
--resource | Filter by resource name (e.g. users, groups, messages) |
--method | Filter by HTTP method |
--limit | Max results (default 20) |
At least one of --query, --resource, or --method is required.
Using with MCP Servers
If the agent has access to a Microsoft Graph MCP server (such as lokka.dev or any other Microsoft Graph MCP server), use the search tools above to find the right endpoint, permissions, and request syntax, then use the information with the MCP server for execution.
In this mode, no authentication through this skill is needed. The skill acts purely as a knowledge layer — the MCP server handles authentication and API execution.
Direct Microsoft Graph API Execution
When no Graph MCP server is available, this skill can authenticate to Microsoft 365 and execute Microsoft Graph API calls directly.
Authentication
The tool supports delegated (user) and app-only (application) authentication, auto-detected from environment variables.
Quick start:
msgraph auth status # check if signed in
msgraph auth signin # sign in (opens browser) - recommended
msgraph auth signin --device-code # sign in via device code (headless)
msgraph auth signout # clear the session- Delegated auth (default): Interactive browser sign-in, with device code fallback for headless environments. Supports incremental consent — on 403, the tool re-authenticates with required scopes and retries automatically.
- App-only auth: Auto-detected when
MSGRAPH_CLIENT_SECRET,MSGRAPH_CLIENT_CERTIFICATE_PATH,MSGRAPH_FEDERATED_TOKEN_FILE, orMSGRAPH_AUTH_METHOD=managed-identityis set. RequiresMSGRAPH_TENANT_ID.
For detailed authentication configuration including certificates, managed identity, workload identity federation, and all environment variables, see references/docs/authentication.md.
Making Graph API Calls
IMPORTANT: Run msgraph auth status before the first graph-call in a session to verify authentication.
msgraph graph-call <METHOD> <URL> [flags]Read Operations
msgraph graph-call GET /me
msgraph graph-call GET /users --select "displayName,mail" --top 10
msgraph graph-call GET /me/messages --filter "isRead eq false" --top 5 --select "subject,from,receivedDateTime"
msgraph graph-call GET /users --filter "startsWith(displayName,'John')"Write Operations
IMPORTANT: YOU MUST ask the user for confirmation before any write operation. Write operations require the --allow-writes flag.
msgraph graph-call POST /me/sendMail --body '{"message":{"subject":"Hello","body":{"content":"Hi"},"toRecipients":[{"emailAddress":{"address":"user@example.com"}}]}}' --allow-writes
msgraph graph-call PATCH /me --body '{"jobTitle":"Engineer"}' --allow-writesDELETE is always blocked regardless of flags.
graph-call Flags
| Flag | Description | Example |
|---|---|---|
--select | OData $select | --select "displayName,mail" |
--filter | OData $filter | --filter "isRead eq false" |
--top | OData $top (limit results) | --top 10 |
--expand | OData $expand | --expand "members" |
--orderby | OData $orderby | --orderby "displayName desc" |
--api-version | v1.0 or beta (default: beta) | --api-version v1.0 |
--scopes | Request additional permission scopes | --scopes "Mail.Read" |
--headers | Custom HTTP headers | --headers "ConsistencyLevel:eventual" |
--body | Request body (JSON) | --body '{"key":"value"}' |
--output | json (default) or raw | --output raw |
--allow-writes | Allow POST/PUT/PATCH (requires user confirmation) |
Critical Rules
Always (search and knowledge)
1. Never guess or fabricate Microsoft Graph endpoints — always verify via search before calling. This skill exists because agents hallucinate endpoints; use it. 2. Use the progressive lookup strategy — start with what you know, then sample-search, api-docs-search, openapi-search as needed. 3. Use `--select` to reduce response size — only request fields you need. 4. Use `--top` to limit results — avoid fetching thousands of records. 5. ConsistencyLevel header is required for $count and $search on directory objects (users, groups, etc.). Use --headers "ConsistencyLevel:eventual". 6. Default API version is beta — use --api-version v1.0 for production-stable endpoints.
When using direct execution (graph-call)
7. Check auth status before the first graph-call in a session. 8. GET is the default — no special flags needed. 9. Write operations require `--allow-writes` — YOU MUST confirm with the user first. 10. DELETE is always blocked — inform the user this is not supported. 11. 403 triggers automatic re-auth — the tool requests additional scopes and retries (delegated auth only). 12. All output is JSON — parse statusCode and body fields from the response.
Error Handling
| Status | Meaning | Action |
|---|---|---|
| 401 | Token expired | Run msgraph auth signin again |
| 403 | Insufficient permissions | Tool auto-retries with incremental consent. If still fails, user needs admin consent. |
| 404 | Resource not found | Verify the endpoint path |
| 429 | Rate limited | Wait for Retry-After duration, then retry |
Environment Variables
| Variable | Description | Default |
|---|---|---|
MSGRAPH_CLIENT_ID | Custom Entra ID app client ID | Microsoft Graph CLI Tools app |
MSGRAPH_TENANT_ID | Target tenant ID (required for app-only) | common |
MSGRAPH_API_VERSION | Default API version | beta |
MSGRAPH_INDEX_DB_PATH | Path to OpenAPI index database | Auto-detected |
MSGRAPH_SAMPLES_DB_PATH | Path to samples index database | Auto-detected |
MSGRAPH_API_DOCS_DB_PATH | Path to API docs index database | Auto-detected |
MSGRAPH_NO_TOKEN_CACHE | Disable persistent token cache (in-memory only) | false |
For the full list of authentication environment variables, see references/docs/authentication.md.
Compatibility
Search tools run fully offline with no network access required. Direct API execution requires network access to login.microsoftonline.com and graph.microsoft.com. A system browser is used for interactive auth; falls back to device code flow in headless environments.
Reference Files
Load these on demand when you need specific guidance. Do NOT load them preemptively.
| File | When to Read | Size |
|---|---|---|
| references/REFERENCE.md | Common resource paths, OData patterns, permission scopes | ~230 lines |
| references/docs/authentication.md | Detailed auth configuration: certificates, managed identity, workload identity, all env vars | ~200 lines |
| references/docs/query-parameters.md | OData $select, $filter, $expand, $top, $orderby, $search syntax and gotchas | ~300 lines |
| references/docs/advanced-queries.md | ConsistencyLevel header, $count, $search, ne/not/endsWith on directory objects | ~190 lines |
| references/docs/paging.md | @odata.nextLink pagination, server-side vs client-side paging | ~50 lines |
| references/docs/batching.md | $batch endpoint, combining multiple requests, dependsOn sequencing | ~280 lines |
| references/docs/throttling.md | 429 handling, Retry-After, backoff strategy | ~90 lines |
| references/docs/errors.md | HTTP status codes, error response format, error codes | ~105 lines |
| references/docs/best-practices.md | $select for performance, pagination, delta queries, batching | ~155 lines |
Contributing to Microsoft Graph Skill
Thank you for your interest in contributing! This document covers how to add community query samples.
Why contribute?
Large language models are trained on data that's months old — they don't know about the latest Graph APIs or endpoint changes. Some Graph API docs and endpoints aren't always clear either. Your working query samples go directly into the skill's search index, helping every AI agent using this skill find the right API call.
Read more: Why Your Samples Matter
Query Samples
The skill includes a library of community-contributed query samples that help AI agents find the right Microsoft Graph API call. Each sample maps a natural-language intent to an exact API query.
File format
Each sample is a YAML file with two fields:
intent: List all Conditional Access policies
query: GET /beta/identity/conditionalAccess/policiesFor multi-step queries:
intent: Get users assigned to the Global Administrator role
query:
- GET /v1.0/roleManagement/directory/roleDefinitions?$filter=displayName eq 'Global Administrator'
- GET /v1.0/roleManagement/directory/roleAssignments?$filter=roleDefinitionId eq '{id from step 1}'&$expand=principalDirectory structure
Place your file in the appropriate product directory under samples/ at the repo root:
samples/
├── entra/ # Entra ID (identity, conditional access, roles, apps)
├── exchange/ # Exchange Online (mail, calendar, mailbox)
├── general/ # Cross-product (licensing, organization, domains)
├── intune/ # Intune (device management, compliance, apps)
├── security/ # Microsoft Security (Defender, alerts, hunting)
├── sharepoint/ # SharePoint Online (sites, lists, documents)
└── teams/ # Microsoft Teams (teams, channels, messages)How to contribute
1. Fork this repository 2. Create a new .yaml file in the appropriate product directory:
samples/{product}/{descriptive-name}.yaml3. Add your intent and query fields 4. Submit a pull request
Or use the web form at graph.pm/improve/add-sample to submit via the browser.
Guidelines
- One sample per file — eliminates merge conflicts when multiple people contribute
- Use descriptive file names —
list-conditional-access-policies.yaml, notsample1.yaml - Be specific in the intent — "List all Conditional Access policies" beats "Get policies"
- Include the full query path —
GET /beta/identity/conditionalAccess/policies - Use `$select` where appropriate — helps agents return only needed fields
- Test your query — verify it works against the Graph API before submitting
When to contribute
If an AI agent struggled to find the right Graph API call and you had to manually construct it, that's the perfect candidate for a new sample.
Build & validation
Run the samples index builder locally to validate your contribution:
make samplesThis compiles all YAML files into skills/msgraph/references/samples-index.json. If your file has syntax errors or is missing required fields, the build will fail with an error message pointing to the problematic file.
You can then test your sample is searchable:
go run . sample-search --query "your intent keywords"License
By contributing, you agree that your contributions will be licensed under the MIT License.
Advanced query capabilities on Microsoft Entra ID objects
Microsoft Graph supports advanced query capabilities on various Microsoft Entra ID objects, also called directory objects, to help you efficiently access data. Examples include the addition of not (not), not equals (ne), and ends with (endsWith) operators on the $filter query parameter.
The Microsoft Graph query engine uses an index store to fulfill query requests. To add support for extra query capabilities on some properties, those properties are indexed in a separate store. This separate indexing improves query performance. However, these advanced query capabilities aren't available by default. The requestor must set the ConsistencyLevel header to eventual and, except for $search, use the $count query parameter. The ConsistencyLevel header and $count are referred to as advanced query parameters.
For example, to retrieve only inactive user accounts, you can run either of these queries that use the $filter query parameter:
Option 1: Use the $filter query parameter with the eq operator. This request works by default and doesn't require the advanced query parameters.
GET https://graph.microsoft.com/v1.0/users?$filter=accountEnabled eq falseOption 2: Use the $filter query parameter with the ne operator. This request isn't supported by default because the ne operator is only supported in advanced queries. Therefore, you must add the ConsistencyLevel header set to eventual and use the $count=true query string.
GET https://graph.microsoft.com/v1.0/users?$filter=accountEnabled ne true&$count=true
ConsistencyLevel: eventualMicrosoft Entra ID (directory) objects that support advanced query capabilities
Advanced query capabilities are supported only on directory objects and their relationships, including the following objects:
| Object | Relationships |
|---|---|
| administrativeUnit | <li>members |
| application | <li>owners |
| appRoleAssignment | - |
| device | <li>memberOf <li>transitiveMemberOf <li>registeredUsers <li>registeredOwners |
| group | <li>members <li>transitiveMembers <li>memberOf <li>transitiveMemberOf <li>owners <li>appRoleAssignments |
| oAuth2PermissionGrant (delegated permission grants) | - |
| orgContact | <li>memberOf <li>transitiveMemberOf |
| servicePrincipal | <li>memberOf <li>owners<li>transitiveMemberOf <li>appRoleAssignments <li>appRoleAssignedTo <li>oAuth2PermissionGrant |
| user | <li>memberOf <li>transitiveMemberOf<li>ownedObjects <li>registeredDevices <li>ownedDevices <li>transitiveManagers <li>directReports <li>transitiveReports <li>appRoleAssignments <li>oAuth2PermissionGrant |
[!NOTE]
Use of$filteron the relationships of the preceding list of directory objects is supported only with advanced query parameters. However, in such cases, don't use$expandin the same request because it isn't supported with advanced query parameters.
Query scenarios that require advanced query capabilities
The following table lists query scenarios on directory objects that advanced queries support:
| Description | Example |
|---|---|
Use of $count as a URL segment | GET ~/groups/$count |
Use of $count as a query string parameter | GET ~/servicePrincipals?$count=true |
Use of $count in a $filter expression | GET ~/users?$filter=assignedLicenses/$count eq 0&$count=true |
Use of $search | GET ~/applications?$search="displayName:Browser" |
Use of $orderby on select properties | GET ~/applications?$orderby=displayName&$count=true |
Use of $filter with the endsWith operator | GET&method=GET&version=v1.0&GraphUrl=https://graph.microsoft.com&headers=W3sibmFtZSI6IkNvbnNpc3RlbmN5TGV2ZWwiLCJ2YWx1ZSI6ImV2ZW50dWFsIn1d) ~/users?$count=true&$filter=endsWith(mail,'@outlook.com') |
Use of $filter and $orderby in the same query | GET%26%24count%3Dtrue&method=GET&version=v1.0&GraphUrl=https://graph.microsoft.com&headers=W3sibmFtZSI6IkNvbnNpc3RlbmN5TGV2ZWwiLCJ2YWx1ZSI6ImV2ZW50dWFsIn1d) ../applications?$orderby=displayName&$filter=startsWith(displayName, 'Box')&$count=true |
Use of $filter with the startsWith operators on specific properties. | GET%20OR%20startsWith(mobilePhone%2C%20'25473')%26%24count%3Dtrue&method=GET&version=v1.0&GraphUrl=https://graph.microsoft.com&headers=W3sibmFtZSI6IkNvbnNpc3RlbmN5TGV2ZWwiLCJ2YWx1ZSI6ImV2ZW50dWFsIn1d) ~/users?$filter=startsWith(mobilePhone, '25478') OR startsWith(mobilePhone, '25473')&$count=true |
Use of $filter with ne and not operators | GET%26%24count%3Dtrue&method=GET&version=v1.0&GraphUrl=https://graph.microsoft.com&headers=W3sibmFtZSI6IkNvbnNpc3RlbmN5TGV2ZWwiLCJ2YWx1ZSI6ImV2ZW50dWFsIn1d) ~/users?$filter=companyName ne null and NOT(companyName eq 'Microsoft')&$count=true |
Use of $filter with not and startsWith operators | GET%26%24count%3Dtrue&method=GET&version=v1.0&GraphUrl=https://graph.microsoft.com&headers=W3sibmFtZSI6IkNvbnNpc3RlbmN5TGV2ZWwiLCJ2YWx1ZSI6ImV2ZW50dWFsIn1d) ~/users?$filter=NOT startsWith(displayName, 'Conf')&$count=true |
Use of $filter on a collection with endsWith operator | GET)%26select%3Did%2CdisplayName%2Cproxyaddresses&method=GET&version=beta&GraphUrl=https://graph.microsoft.com&headers=W3sibmFtZSI6IkNvbnNpc3RlbmN5TGV2ZWwiLCJ2YWx1ZSI6ImV2ZW50dWFsIn1d) ~/users?$count=true&$filter=proxyAddresses/any (p:endsWith(p, 'contoso.com'))&$select=id,displayName,proxyaddresses |
| Use of OData cast with transitive members list | GET ~/me/transitiveMemberOf/microsoft.graph.group?$count=true |
[!NOTE]
>
+ You can use$filterand$orderbytogether only with advanced queries.
+ Advanced queries don't currently support $expand.+ Azure AD B2C tenants don't currently support advanced query capabilities.
+ To use advanced query capabilities in batch requests, specify the ConsistencyLevel header in the JSON body of the POST request.Support for filter by properties of Microsoft Entra ID (directory) objects
Support for sorting by properties of Microsoft Entra ID (directory) objects
Error handling for advanced queries on directory objects
The following section provides examples of common error scenarios when you don't use advanced query parameters where required.
You can count directory objects only by using the advanced queries parameters. If you don't specify the ConsistencyLevel=eventual header, the request returns an error when you use the $count URL segment (/$count) or silently ignores the $count query parameter (?$count=true) if you use it.
GET https://graph.microsoft.com/v1.0/users/$count{
"error": {
"code": "Request_BadRequest",
"message": "$count is not currently supported.",
"innerError": {
"date": "2021-05-18T19:03:10",
"request-id": "d9bbd4d8-bb2d-44e6-99a1-71a9516da744",
"client-request-id": "539da3bd-942f-25db-636b-27f6f6e8eae4"
}
}
}For directory objects, $search works only in advanced queries. If you don't specify the ConsistencyLevel header, the request returns an error.
GET https://graph.microsoft.com/v1.0/applications?$search="displayName:Browser"{
"error": {
"code": "Request_UnsupportedQuery",
"message": "Request with $search query parameter only works through MSGraph with a special request header: 'ConsistencyLevel: eventual'",
"innerError": {
"date": "2021-05-27T14:26:47",
"request-id": "9b600954-ba11-4899-8ce9-6abad341f299",
"client-request-id": "7964ef27-13a3-6ca4-ed7b-73c271110867"
}
}
}If a property or query parameter in the URL supports only advanced queries but either the ConsistencyLevel header or the $count=true query string is missing, the request returns an error.
GET https://graph.microsoft.com/beta/users?$filter=endsWith(userPrincipalName,'%23EXT%23@contoso.com'){
"error": {
"code": "Request_UnsupportedQuery",
"message": "Operator 'endsWith' is not supported because the required parameters might be missing. Try adding $count=true query parameter and ConsistencyLevel:eventual header. Refer to https://aka.ms/graph-docs/advanced-queries for more information",
"innerError": {
"date": "2023-07-14T08:43:39",
"request-id": "b3731da7-5c46-4c37-a8e5-b190124d2531",
"client-request-id": "a1556ddf-4794-929d-0105-b753a78b4c68"
}
}
}If a property isn't indexed to support a query parameter, the request returns an error even if advanced query parameters are specified. For example, the createdDateTime property of the group resource isn't indexed for query capabilities.
GET https://graph.microsoft.com/beta/groups?$filter=createdDateTime ge 2021-11-01&$count=true
ConsistencyLevel: eventual{
"error": {
"code": "Request_UnsupportedQuery",
"message": "Unsupported or invalid query filter clause specified for property 'createdDateTime' of resource 'Group'.",
"innerError": {
"date": "2023-07-14T08:42:44",
"request-id": "b6a5f998-94c8-430d-846d-2eaae3031492",
"client-request-id": "2be83e05-649e-2508-bcd9-62e666168fc8"
}
}
}However, a request that includes query parameters might fail silently. For example, the request might fail for unsupported query parameters and for unsupported combinations of query parameters. In these cases, examine the data returned by the request to determine whether the query parameters you specified had the desired effect. For example, in the following example, the @odata.count parameter is missing even if the query is successful.
GET https://graph.microsoft.com/v1.0/users?$count=trueHTTP/1.1 200 OK
Content-type: application/json
{
"@odata.context":"https://graph.microsoft.com/v1.0/$metadata#users",
"value":[
{
"displayName":"Oscar Ward",
"mail":"oscarward@contoso.com",
"userPrincipalName":"oscarward@contoso.com"
}
]
}Authentication
This reference covers detailed authentication configuration for the msgraph skill. For a quick overview, see the Authentication section in the main SKILL.md.
Auth Method Detection
The auth method is auto-detected from environment variables in priority order:
1. MSGRAPH_CLIENT_SECRET set → Client secret 2. MSGRAPH_CLIENT_CERTIFICATE_PATH set → Certificate 3. MSGRAPH_FEDERATED_TOKEN_FILE (or AZURE_FEDERATED_TOKEN_FILE / AWS_WEB_IDENTITY_TOKEN_FILE) set → Workload identity 4. MSGRAPH_AUTH_METHOD=managed-identity → Managed identity 5. None of the above → Delegated (interactive browser / device code)
Delegated Auth
Used when no app-only env vars are set. A user signs in interactively.
Interactive Browser (default)
Opens the system browser for sign-in. This is the default when a browser is available.
msgraph auth signinDevice Code
For headless environments (SSH, containers, CI), use device code flow:
msgraph auth signin --device-codeThe tool prints a URL and code to stderr. Open the URL in any browser, enter the code, and authenticate.
Auto-detection: if the tool detects an SSH session or no display server, it automatically falls back to device code.
Requesting Specific Scopes
You can request specific scopes at sign-in:
msgraph auth signin --scopes "Mail.Read,Calendars.Read"This is useful when you know upfront what permissions you'll need.
Incremental Consent
When a Graph API call returns 403 Forbidden, the tool:
1. Parses the error message to extract required permission scopes (regex: [A-Z][a-zA-Z]+\.[A-Z][a-zA-Z]+) 2. Merges new scopes with existing scopes 3. Re-authenticates with the combined scope set 4. Retries the original request with the new token
This happens transparently — no manual scope management needed.
Session-Scoped Cache
Tokens are cached in a session-scoped temporary file (os.TempDir()) for the duration of the session. The cache is keyed by client ID and tenant ID. No credentials are persisted permanently. The cache file is automatically cleaned up on sign-out.
App-Only Auth
For automation, CI/CD pipelines, and service-to-service scenarios.
IMPORTANT: App-only auth requires MSGRAPH_TENANT_ID set to a specific tenant (not common). The tool errors early with a clear message if this is missing. Incremental consent is not available — all permissions must be pre-configured and admin-consented in the Entra ID app registration.
All pre-granted application permissions are used via the https://graph.microsoft.com/.default scope. The --device-code and --scopes flags are ignored for app-only auth.
Client Secret
The simplest app-only method. Set the secret from your Entra ID app registration:
export MSGRAPH_CLIENT_ID="your-app-id"
export MSGRAPH_TENANT_ID="contoso.onmicrosoft.com"
export MSGRAPH_CLIENT_SECRET="your-secret-value"
msgraph auth signinSetup in Entra ID: 1. Go to App Registrations > your app > Certificates & secrets 2. Add a new client secret 3. Copy the secret value (it is only shown once) 4. Under API permissions, add the Microsoft Graph Application permissions you need and grant admin consent
Client Certificate
More secure than client secrets — uses a certificate for authentication:
export MSGRAPH_CLIENT_ID="your-app-id"
export MSGRAPH_TENANT_ID="contoso.onmicrosoft.com"
export MSGRAPH_CLIENT_CERTIFICATE_PATH="/path/to/cert.pem"
msgraph auth signinThe PEM file must contain both the certificate and private key. RSA, ECDSA, and PKCS#8 private keys are supported.
If the private key is encrypted:
export MSGRAPH_CLIENT_CERTIFICATE_PASSWORD="key-password"Setup in Entra ID: 1. Go to App Registrations > your app > Certificates & secrets 2. Upload the public certificate (.cer or .pem) 3. Under API permissions, add the Microsoft Graph Application permissions you need and grant admin consent
Managed Identity
For workloads running on Azure (VMs, App Service, Azure Functions, AKS):
export MSGRAPH_AUTH_METHOD="managed-identity"
msgraph auth signinFor user-assigned managed identities, also set the client ID:
export MSGRAPH_AUTH_METHOD="managed-identity"
export MSGRAPH_MANAGED_IDENTITY_CLIENT_ID="your-managed-identity-client-id"
msgraph auth signinNo client secret or certificate is needed — Azure handles credential management automatically.
Setup: 1. Enable managed identity on your Azure resource 2. In the Entra ID enterprise application for the managed identity, assign the Microsoft Graph app roles you need (via PowerShell or Azure CLI)
Workload Identity Federation
For workloads running outside Azure (GitHub Actions, GCP, AWS, Kubernetes) that exchange a platform token for a Microsoft Entra ID token:
export MSGRAPH_CLIENT_ID="your-app-id"
export MSGRAPH_TENANT_ID="contoso.onmicrosoft.com"
export MSGRAPH_FEDERATED_TOKEN_FILE="/var/run/secrets/token"
msgraph auth signinThe tool also auto-reads these standard environment variables:
AZURE_FEDERATED_TOKEN_FILE— set by AKS workload identityAWS_WEB_IDENTITY_TOKEN_FILE— set by AWS IRSA / EKS
For AKS workload identity, AZURE_CLIENT_ID and AZURE_TENANT_ID are used as fallbacks if MSGRAPH_CLIENT_ID / MSGRAPH_TENANT_ID are not set.
The token file is re-read on each token acquisition, so token rotation is handled automatically.
Setup in Entra ID: 1. Go to App Registrations > your app > Certificates & secrets > Federated credentials 2. Add a federated credential for your platform (GitHub Actions, Kubernetes, etc.) 3. Under API permissions, add the Microsoft Graph Application permissions you need and grant admin consent
Auth Commands
| Command | Description |
|---|---|
msgraph auth signin | Sign in (delegated) or verify credentials (app-only) |
msgraph auth signin --device-code | Force device code flow (delegated only) |
msgraph auth signin --scopes "Mail.Read,Calendars.Read" | Request specific scopes (delegated only) |
msgraph auth signout | Clear the current session |
msgraph auth status | Check sign-in state and account info |
msgraph auth switch-tenant <tenant-id> | Switch to a different M365 tenant |
Custom Client ID
By default, msgraph uses the Microsoft Graph Command Line Tools app ID (14d82eec-204b-4c2f-b7e8-296a70dab67e). This is a first-party Microsoft app pre-registered in most M365 tenants.
To use your own Entra ID app registration:
export MSGRAPH_CLIENT_ID="your-custom-app-id"
msgraph auth signinAll Authentication Environment Variables
| Variable | Description | Default |
|---|---|---|
MSGRAPH_CLIENT_ID | Custom Entra ID app client ID | Microsoft Graph CLI Tools app |
MSGRAPH_TENANT_ID | Target tenant ID (required for app-only) | common |
MSGRAPH_CLIENT_SECRET | App registration client secret | — |
MSGRAPH_CLIENT_CERTIFICATE_PATH | Path to PEM certificate file | — |
MSGRAPH_CLIENT_CERTIFICATE_PASSWORD | Password for encrypted certificate key | — |
MSGRAPH_AUTH_METHOD | Set to managed-identity for Azure managed identity | — |
MSGRAPH_MANAGED_IDENTITY_CLIENT_ID | Client ID for user-assigned managed identity | — |
MSGRAPH_FEDERATED_TOKEN_FILE | Path to federated token file (workload identity) | — |
MSGRAPH_NO_TOKEN_CACHE | Disable persisted token cache; tokens live only for the current process | false |
Also auto-reads: AZURE_FEDERATED_TOKEN_FILE, AWS_WEB_IDENTITY_TOKEN_FILE, AZURE_CLIENT_ID, AZURE_TENANT_ID.
Silent Token Acquisition
After initial sign-in, tokens are cached. Subsequent calls use silent acquisition:
1. Check the in-memory/file cache for a valid token 2. If expired, MSAL automatically refreshes using the refresh token (delegated) or re-acquires via credentials (app-only) 3. Only if refresh fails does it prompt for interactive auth (delegated only)
Combine multiple HTTP requests using JSON batching
JSON batching allows clients to combine multiple requests into a single JSON object and a single HTTP call, reducing network roundtrips and improving efficiency. Microsoft Graph supports batching up to 20 requests into the JSON object.
In this article, we explore the basics of JSON batching, how it works, and how you can use it to optimize your applications.
[!NOTE]
Microsoft Graph implements the $batch OData URL path segment to support JSON batching.Example scenario
Consider a client that wants to compose a view of the following unrelated data:
- An image stored in OneDrive
- A list of Planner tasks
- The calendar for a group
Combining these three individual requests into a single batch request can save the application significant network latency.
Creating a batch request
To create a batch request:
1. Specify the request HTTP method as POST. 1. Specify the URL endpoint, whether it targets the v1.0 or beta version of Microsoft Graph, and append the $batch segment to the URL. That is, https://graph.microsoft.com/v1.0/$batch. 1. Define the batch request body as follows: 1. A JSON batch request body consists of a single JSON object with one required property: requests. This property is a collection of individual requests. 2. For each individual request, the following properties can be passed.
| Property | Description |
|---|---|
| id | Required. String. A correlation value to associate individual responses with requests. This value allows the server to process requests in the batch in the most efficient order. Not case-sensitive. Must be unique in the batch, otherwise, the batch request fails with a 400 error code. |
| method | Required. The HTTP method supported for the request specified in url. |
| url | Required. The relative resource URL for the individual request. Therefore, while the absolute URL is https://graph.microsoft.com/v1.0/users, this url is /users. |
| headers | Optional but required when the body is specified. A JSON object with the key/value pair for the headers. For example, when the ConsistencyLevel header is required, this property is represented as "headers": {"ConsistencyLevel": "eventual"}. When the body is supplied, a Content-Type header must be included. |
| body | Optional. Might be a JSON object or a base64 URL-encoded value, for example, when the body is an image. When a body is included with the request, the headers object must contain a value for Content-Type. |
Example JSON batch request
In this example scenario, you construct the JSON batch request. The individual requests aren't interdependent and therefore can be placed into the batch request in any order.
POST https://graph.microsoft.com/v1.0/$batch
Accept: application/json
Content-Type: application/json
{
"requests": [
{
"id": "1",
"method": "GET",
"url": "/me/memberOf"
},
{
"id": "2",
"method": "GET",
"url": "/me/planner/tasks"
},
{
"id": "3",
"method": "DELETE",
"url": "/groups/0e226165-c685-41ce-8bfc-df8360ab325d"
},
{
"id": "4",
"url": "/users/161ab652-cdbc-490d-82a4-0ada1f0db247/getPasswordSingleSignOnCredentials",
"method": "POST",
"body": {},
"headers": {"Content-Type": "application/json"}
},
{
"id": "5",
"url": "users?$select=id,displayName,userPrincipalName&$filter=city eq null&$count=true",
"method": "GET",
"headers": {
"ConsistencyLevel": "eventual"
}
}
]
}Processing the JSON batch response
The response format for JSON batch requests differs from the request format as follows:
- The property in the main JSON object is named responses as opposed to requests.
- Individual responses might appear in a different order than the requests. The id property can be used to correlate individual requests and responses.
- Rather than method and url, individual responses have a status property. The value of status is the HTTP status code.
- The headers property in each individual response represents the headers returned by the server, for example, Cache-Control and Content-Type headers.
The status code on a batch response is typically 200 or 4xx. If the batch request itself is malformed, the status code is 400. If the batch request is parseable, the status code is 200. A 200 status code on the batch response headers doesn't indicate that the individual requests inside the batch succeeded. This is why each individual response in the responses property has a status code.
Example JSON batch response
For the previous example, assume this response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"responses": [
{
"id": "1",
"status": 200,
"headers": {
"Cache-Control": "no-cache",
"x-ms-resource-unit": "1",
"OData-Version": "4.0",
"Content-Type": "application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8"
},
"body": {
"@odata.context": "https://graph.microsoft.com/beta/$metadata#directoryObjects",
"@odata.nextLink": "https://graph.microsoft.com/beta/me/memberOf?$top=1&$skiptoken=RFNwdAoAAQAAAAAAAAAAFAAAAI45VMy0CO9Ei1L3Lr1q95UBAAAAAAAAAAAAAAAAAAAXMS4yLjg0MC4xMTM1NTYuMS40LjIzMzEGAAAAAAABURXWGePFEEGbudEn3SOTuQEDAQAAAQAAAAA",
"value": [
{
"@odata.type": "#microsoft.graph.directoryRole",
"id": "21004afc-7bb2-4fe6-a1e1-074ebd3e52c1",
"deletedDateTime": null,
"description": "Can manage all aspects of users and groups, including resetting passwords for limited admins.",
"displayName": "User Administrator",
"roleTemplateId": "fe930be7-5e62-47db-91af-98c3a49a38b1"
}
]
}
},
{
"id": "2",
"status": 403,
"headers": {
"Cache-Control": "no-cache",
"X-ProxyCluster": "wus-001.tasks.osi.office.net",
"X-OfficeCluster": "wus-001.tasks.osi.office.net",
"X-Tasks-CorrelationId": "18a8e521-78a4-4129-9b6b-d678116464e7",
"Content-Type": "application/json"
},
"body": {
"error": {
"code": "",
"message": "You do not have the required permissions to access this item.",
"innerError": {
"date": "2025-02-13T10:17:05",
"request-id": "93b6f17e-c05d-4f45-ad2a-6665c708d8a0",
"client-request-id": "e70c5c1b-8b47-68c0-3171-3d22f5e0bd54"
}
}
}
},
{
"id": "3",
"status": 403,
"headers": {
"Cache-Control": "no-cache",
"x-ms-resource-unit": "1",
"Content-Type": "application/json"
},
"body": {
"error": {
"code": "Authorization_RequestDenied",
"message": "Insufficient privileges to complete the operation.",
"innerError": {
"date": "2025-02-13T10:17:06",
"request-id": "93b6f17e-c05d-4f45-ad2a-6665c708d8a0",
"client-request-id": "e70c5c1b-8b47-68c0-3171-3d22f5e0bd54"
}
}
}
},
{
"id": "4",
"status": 405,
"headers": {
"Cache-Control": "no-cache",
"x-ms-resource-unit": "1",
"Content-Type": "application/json"
},
"body": {
"error": {
"code": "Request_BadRequest",
"message": "Specified HTTP method is not allowed for the request target.",
"innerError": {
"date": "2025-02-13T10:21:18",
"request-id": "3a3b1bf7-3596-4493-8264-de81e028071f",
"client-request-id": "e5f9a304-2796-b7e8-ccce-dd989953ebc4"
}
}
}
},
{
"id": "5",
"status": 200,
"headers": {
"Cache-Control": "no-cache",
"x-ms-resource-unit": "1",
"OData-Version": "4.0",
"Content-Type": "application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8"
},
"body": {
"@odata.context": "https://graph.microsoft.com/beta/$metadata#users(id,displayName,userPrincipalName)",
"@odata.count": 36,
"value": [
{
"id": "10a1d484-cd1a-4162-a5a4-832370bac356",
"displayName": "Lynne Robbins",
"userPrincipalName": "LynneR@contoso.com"
}
]
}
}
]
}Explanation of individual responses in the example batch response
- Requests 1 and 5 succeeded as shown by the
200status code. - Requests 2 and 3 failed with a
403status code because the caller didn't have the required permissions. - Request 4 failed with a
405status code because the endpoint specified in the url property of the request is currently inbetaonly yet the request URL targets thev1.0endpoint of Microsoft Graph. While the target URL doesn't require a request body, you must still specify the headers and body paremeters where only body can be an empty object.
Sequencing requests with the dependsOn property
You can specify the requests in the batch to be executed in a specified order by using the dependsOn property. This property is an array of strings that references the id of a different individual request. For example, in the following request, the client is specifying that requests should be run in the order request 1 then request 2, then request 4, then request 3.
{
"requests": [
{
"id": "1",
"method": "GET",
"url": "..."
},
{
"id": "2",
"dependsOn": [ "1" ],
"method": "GET",
"url": "..."
},
{
"id": "4",
"dependsOn": [ "2" ],
"method": "GET",
"url": "..."
},
{
"id": "3",
"dependsOn": [ "4" ],
"method": "GET",
"url": "..."
}
]
}If an individual request fails, any request that depends on that request fails with status code 424 (Failed Dependency).
[!TIP]
Batch should be either fully sequential or fully parallel.
Bypassing URL length limitations with batching
Another use case for JSON batching is to bypass URL length limitations. In cases where the filter clause is complex, the URL length might surpass limitations built into browsers or other HTTP clients. You can use JSON batching as a workaround for running these requests because the lengthy URL simply becomes part of the request payload.
Batch size limitations
- JSON batch requests are currently limited to 20 individual requests.
- Depending on the APIs that are part of the batch request, the underlying services impose their own throttling limits that affect applications that use Microsoft Graph to access them.
- Requests in a batch are evaluated individually against the applicable throttling limits and if any request exceeds the limits, it fails with a status of
429.
For more information, see [Throttling and batching][throttling-and-batching].
Known issues
For a list of current limitations related to batching, see known issues.
Best practices for working with Microsoft Graph
This article describes best practices that you can apply to help your applications get the most out of Microsoft Graph—whether that involves learning about Microsoft Graph, improving app performance, or making your application more reliable for end users.
Use Graph Explorer to get to know the API
The easiest way to start exploring the data available through Microsoft Graph is to use Graph Explorer. Graph Explorer lets you craft REST requests (with full CRUD support), adapt the HTTP request headers, and see the data responses. To help you get started, Graph Explorer also provides a set of sample queries.
Experiment with new APIs before you integrate them into your application.
Authentication
To access data through Microsoft Graph, your application needs to acquire an OAuth 2.0 access token, and present it to Microsoft Graph in either of the following options:
- The HTTP Authorization request header, as a Bearer token
- The graph client constructor, when using a Microsoft Graph client library
Use the Microsoft Authentication Library (MSAL) to acquire the access token to Microsoft Graph.
Consent and authorization
Apply the following best practices for consent and authorization in your app:
- Apply least privilege. Grant users and apps only the lowest privileged permission they require to call the API. Check the permissions section in the method topics (for example, see creating a user), and choose the least privileged permissions. For example, if the app will read only the profile of the currently signed-in user, grant User.Read instead of User.ReadBasic.All. If an app doesn't read the user's calendar, don't grant it the Calendars.Read permission. For a full list of permissions, see permissions reference.
- Use the correct permission type based on scenarios. Avoid using both application and delegated permissions in the same app. If you're building an interactive application where a signed-in user is present, your application should use delegated permissions. If, however, your application runs without a signed-in user, such as a background service or daemon, your application should use application permissions.
[!CAUTION]
Using application permissions for interactive scenarios can put your application at compliance and security risk. It can inadvertently elevate a user's privileges to access data, bypassing policies configured by an administrator.
- Be thoughtful when configuring your app. This will directly affect end user and admin experiences, along with application adoption and security. For example:
- Your application's name, logo, domain, publisher verification status, privacy statement, and terms of use show up in consent and other experiences. Configure these settings carefully so they're understood by your end users.
- Consider who will be consenting to your application - either end users or administrators - and configure your application to request permissions appropriately.
- Ensure that you understand the difference between static, dynamic, and incremental consent.
- Consider multi-tenant applications. Expect customers to have various application and consent controls in different states. For example:
- Tenant administrators can disable the ability for end users to consent to applications. In this case, an administrator would need to consent on behalf of their users.
- Tenant administrators can set custom authorization policies such as blocking users from reading other user's profiles, or limiting self-service group creation to a limited set of users. In this case, your application should expect to handle
403 Forbiddenerror response when acting on behalf of a user.
Handle responses effectively
Depending on the requests you make to Microsoft Graph, your applications should be prepared to handle different types of responses. The following are some of the most important practices to follow to ensure that your application behaves reliably and predictably for your end users.
Pagination
When querying a resource collection, you should expect that Microsoft Graph will return the result set in multiple pages, due to server-side page size limits. When a result set spans multiple pages, Microsoft Graph returns an @odata.nextLink property in the response that contains a URL to the next page of results.
For example, listing the signed-in users messages:
GET https://graph.microsoft.com/v1.0/me/messagesWould return a response containing an @odata.nextLink property, if the result set exceeds the server-side page size limit.
"@odata.nextLink": "https://graph.microsoft.com/v1.0/me/messages?$skip=23"[!NOTE]
Your application should always handle the possibility that the responses are paged in nature, and use the@odata.nextLinkproperty to obtain the next paged set of results, until all pages of the result set have been read. The final page will not contain an@odata.nextLinkproperty. You should include the entire URL in the@odata.nextLinkproperty in your request for the next page of results, treating the entire URL as an opaque string.
For more information, see paging.
Handling expected errors
While your application should handle all error responses (in the 400 and 500 ranges), pay special attention to certain expected errors and responses, listed in the following table.
| Topic | HTTP error code | Best practice |
|---|---|---|
| User doesn't have access | 403 | If your application is up and running, it could encounter this error even if it has been granted the necessary permissions through a consent experience. In this case, it's most likely that the signed-in user doesn't have privileges to access the resource requested. Your application should provide a generic "Access denied" error back to the signed-in user. |
| Not found | 404 | In certain cases, a requested resource might not be found. For example, a resource might not exist, because it hasn't yet been provisioned (like a user's photo) or because it has been deleted. Some deleted resources might be fully restored within 30 days of deletion - such as user, group and application resources, so your application should also take this into account. |
| Throttling | 429 | APIs might throttle at any time for various reasons, so your application must always be prepared to handle 429 responses. This error response includes the Retry-After field in the HTTP response header. Backing off requests using the Retry-After delay is the fastest way to recover from throttling. For more information, see throttling. |
| Service unavailable | 503 | This is likely because the services are busy. You should employ a back-off strategy similar to 429. Additionally, you should always make new retry requests over a new HTTP connection. |
Handling future members in evolvable enumerations
Adding members to existing enumerations can break applications already using these enums. Evolvable enums is a mechanism that Microsoft Graph API uses to add new members to existing enumerations without causing a breaking change for applications.
Evolvable enums have a common _sentinel_ member called unknownFutureValue that demarcates known members that have been defined in the enum initially, and unknown members that are added subsequently or will be defined in the future. Internally, known members are mapped to numeric values that are less than the sentinel member, and unknown members are greater than the sentinel member. The documentation for an evolvable enum lists the possible _string_ values in ascending order: known members, followed by unknownFutureValue, followed by unknown members. Like other types of enumerations, you should _always_ reference members of evolvable enums by their _string_ values.
By default, a GET operation returns only known members for properties of evolvable enum types and your application needs to handle only the known members. If you design your application to handle unknown members as well, you can opt in to receive those members by using an HTTP Prefer request header:
Prefer: include-unknown-enum-membersStoring data locally
Your application should ideally make calls to Microsoft Graph to retrieve data in real time as necessary. You should only cache or store data locally if required for a specific scenario, and if that use case is covered by your terms of use and privacy policy, and does not violate the Microsoft APIs Terms of Use. Your application should also implement proper retention and deletion policies.
Optimizations
In general, for performance and even security or privacy reasons, you should only get the data your application really needs, and nothing more.
Use projections
Choose only the properties your application really needs and no more, because this saves unnecessary network traffic and data processing in your application (and in the service).
[!NOTE]
Use the $select query parameter to limit the properties returned by a query to those needed by your application.For example, when retrieving the messages of the signed-in user, you can specify that only the from and subject properties be returned:
GET https://graph.microsoft.com/v1.0/me/messages?$select=from,subjectWhen you make a GET request without using $select to limit the amount of properties data, Microsoft Graph includes a @microsoft.graph.tips property that provides a best practice recommendation for using $select similar to the following message:
"@microsoft.graph.tips": "Use $select to choose only the properties your app needs, as this can lead to performance improvements. For example: GET groups?$select=appMetadata,assignedLabels",Getting minimal responses
For some operations, such as PUT and PATCH (and in some cases POST), if your application doesn't need to make use of a response payload, you can ask the API to return minimal data. Some services already return a 204 No Content response for PUT and PATCH operations.
[!NOTE]
Request minimal representation responses using the Prefer header set toreturn=minimal, where supported. For creation operations, use of this header is not appropriate because your application expects to get the service generatedidfor the newly created object in the response.
Track changes: delta query and webhook notifications
If your application needs to know about changes to data, you can get a webhook notification whenever data of interest has changed. This is more efficient than simply polling regularly.
Use webhook notifications to get push notifications when data changes.
If your application is required to cache or store Microsoft Graph data locally, and keep that data up to date, or track changes to data for any other reasons, you should use delta query. This avoids excessive computation by your application to retrieve data your application already has, minimize network traffic, and reduce the likelihood of reaching a throttling threshold.
Use delta query to efficiently keep data up to date.
Using webhooks and delta query together
Webhooks and delta query are often used better together, because if you use delta query alone, you need to figure out the right polling interval - too short and this leads to empty responses, which waste resources, too long and you end up with stale data. If you use webhook notifications as the trigger to make delta query calls, you get the best of both worlds.
Use webhook notifications as the trigger to make delta query calls. You should also ensure that your application has a backstop polling threshold, in case no notifications are triggered.
Batching
JSON batching allows you to optimize your application by combining multiple requests into a single JSON object. Combining individual requests into a single batch request can save the application significant network latency and can conserve connection resources.
Use batching where significant network latency can have a significant impact on the performance.
Reliability and support
To ensure reliability and facilitate support for your application:
- Use TLS 1.3 or 1.2 to support all capabilities of Microsoft Graph. Migrate from TLS 1.0 and 1.1. For more information, see Enable support for TLS 1.2 in your environment.
- Honor DNS TTL and set connection TTL to match it. This ensures availability in case of failovers.
- Open connections to all advertised DNS answers.
- Generate a unique GUID and send it on each Microsoft Graph REST request. This helps Microsoft investigate any errors more easily if you need to report an issue with Microsoft Graph.
- On every request to Microsoft Graph, generate a unique GUID, send it in the
client-request-idHTTP request header, and also log it in your application's logs. - Always log the full HTTP Graph API call including the full URL, all the headers and JSON body for both the response and the request. These are required when reporting issues in Microsoft Q&A or to Microsoft Support.
- If you're using a third-party application, the app vendor needs to engage with our support team to investigate the issue.
Microsoft Graph error responses and resource types
Errors in Microsoft Graph are returned using standard HTTP status codes, and a JSON error response object.
HTTP status codes
The following table lists and describes the HTTP status codes that can be returned.
| Status code | Status message | Description |
|---|---|---|
| 400 | Bad Request | Can't process the request because it's malformed or incorrect. |
| 401 | Unauthorized | Required authentication information is either missing or not valid for the resource. |
| 402 | Payment Required | The payment requirements for the API haven't been met. |
| 403 | Forbidden | Access is denied to the requested resource. The user does not have enough permission or does not have a required license. <br /><br /> Important: If conditional access policies are applied to a resource, an HTTP 403; Forbidden error=insufficient_claims message is returned. For more information on Microsoft Graph and conditional access, see Developer Guidance for Microsoft Entra Conditional Access. |
| 404 | Not Found | The requested resource doesn’t exist. |
| 405 | Method Not Allowed | The HTTP method in the request isn't allowed on the resource. |
| 406 | Not Acceptable | This service doesn’t support the format requested in the Accept header. |
| 409 | Conflict | The request can't be processed due to a conflict with the current state. For example, the specified parent folder might not exist. For a Directory_ConcurrencyViolation exception, you can repeat the request after some delay. The exponential backoff retry mechanism can be used. If a Retry-After header is present, that value can be used for the delay between retries. |
| 410 | Gone | The requested resource is no longer available at the server. |
| 411 | Length Required | A Content-Length header is required on the request. |
| 412 | Precondition Failed | A precondition provided in the request (such as an if-match header) doesn't match the resource's current state. |
| 413 | Request Entity Too Large | The request size exceeds the maximum limit. |
| 415 | Unsupported Media Type | The content type of the request is a format that isn't supported by the service. |
| 416 | Requested Range Not Satisfiable | The specified byte range is invalid or unavailable. |
| 422 | Unprocessable Entity | Can't process the request because it is semantically incorrect. |
| 423 | Locked | The resource that is being accessed is locked. |
| 429 | Too Many Requests | Client application has been throttled and shouldn't attempt to repeat the request until an amount of time has elapsed. |
| 500 | Internal Server Error | There was an internal server error while processing the request. |
| 501 | Not Implemented | The requested feature isn’t implemented. |
| 503 | Service Unavailable | The service is temporarily unavailable for maintenance or is overloaded. You can repeat the request after a delay, the length of which can be specified in a Retry-After header. |
| 504 | Gateway Timeout | The server, while acting as a proxy, didn't receive a timely response from the upstream server it needed to access in attempting to complete the request. |
| 507 | Insufficient Storage | The maximum storage quota has been reached. |
| 509 | Bandwidth Limit Exceeded | Your app has been throttled for exceeding the maximum bandwidth cap. Your app can retry the request again after more time has elapsed. |
The error response is a single JSON object that contains a single property named error. This object includes all the details of the error. You can use the information returned here instead of or in addition to the HTTP status code. The following is an example of a full JSON error body.
{
"error": {
"code": "badRequest",
"message": "Uploaded fragment overlaps with existing data.",
"innerError": {
"code": "invalidRange",
"request-id": "request-id",
"date": "date-time"
}
}
}Error resource type
The error resource is returned whenever an error occurs in the processing of a request.
Error responses follow the definition in the Microsoft REST API Guidelines.
JSON representation
The error resource is composed of a single resource:
{
"error": {
"code": "string",
"message": "string",
"innererror": {
"code": "string"
},
"details": []
}
}| Property name | Value | Description |
|---|---|---|
| code | string | An error code string for the error that occurred |
| message | string | A developer ready message about the error that occurred. This shouldn't be displayed to the user directly. |
| innererror | error object | Optional. An additional error object that is more specific than the top-level error. |
| details | error object | Optional. A list of more error objects that provide a breakdown of multiple errors encountered while processing the request. |
Properties
The code property contains a machine-readable value that you can take a dependency on in your code.
The innererror object can recursively contain more innererror objects with more specific error codes properties. When handling an error, apps should loop through all the nested error codes that are available and use the most detailed one that they understand.
The message property is a human-readable value that describes the error condition. Don't take any dependency on the content of this value in your code.
The message property at the root contains an error message intended for the developer to read. Error messages aren't localized and shouldn't be displayed directly to the user. When handling errors, your code shouldn't take any dependency on the message property values because they can change at any time, and they often contain dynamic information specific to the failed request. You should only code against error codes returned in code properties.
The details property is an optional array of error objects that have the same JSON format as the top-level error object. If a request is composed of multiple operations, such as a bulk or batch operation, it is necessary to return an independent error for each operation. In this case, the details list is populated with these individual errors.
Paging Microsoft Graph data in your app
Paging involves requesting or receiving data in batches. It's a performance technique that's crucial for efficiently handling large datasets and that helps improve the performance of your app and the response time of Microsoft Graph.
Some GET queries against Microsoft Graph return multiple pages of data either due to server-side paging or client-side paging. In this article, we explore how paging works for Microsoft Graph and how you can use it to optimize your applications.
[!NOTE]
If you're looking for information about paging in Microsoft Graph SDKs, see Page through a collection using the Microsoft Graph SDKs.
Learn more about pagination through the following video.
How paging works
Server-side paging
In server-side paging, the Microsoft Graph service returns a default number of results in a single page without the client specifying the number of results to return using $top. For example, the GET /users endpoint returns a default of 100 results in a single page.
When there's at least one more page of data available, Microsoft Graph returns an @odata.nextLink property in the response that contains a URL to the next page of results. You use this URL to query for the next page of results. Microsoft Graph will continue to return a reference to the next page of results in the @odata.nextLink property with each response until there are no more pages of results to retrieve. To read all results, you must continue to call Microsoft Graph with the @odata.nextLink property returned in each response until the @odata.nextLink property is no longer returned.
Client-side paging
In client-side paging, a client app specifies the number of results it wants Microsoft Graph to return in a single page by using the $top, $skip, or $skipToken query parameters. Support for client-side paging, including the number of results that the client can request for in a single page depends on the API and the query being performed. For example, the /users endpoint supports $top but not $skip.
The rest of this article describes how to implement client-side paging.
Implementing client-side paging
The following example shows client-side paging where the client uses the $top query parameter to request up to five users in the tenant.
GET https://graph.microsoft.com/v1.0/users?$top=5If the result contains more results, Microsoft Graph returns an @odata.nextLink property similar to the following along with the first page of results:
"@odata.nextLink": "https://graph.microsoft.com/v1.0/users?$top=5&$skiptoken=RFNwdAIAAQAAAD8...AAAAAAAA"Use the entire URL in the @odata.nextLink property in a GET request to retrieve the next page of results. Depending on the API that the query is being performed against, the @odata.nextLink URL value contains either a $skiptoken or a $skip query parameter. Any other query parameters that were present in the original request are also encoded in this URL. Don't try to extract the $skiptoken or $skip value and use it in a different request.
Paging behavior varies across different Microsoft Graph APIs. Consider the following points when working with paged data:
- A page of results might contain zero or more results.
- Different APIs might have different default and maximum page sizes.
- Different APIs might behave differently if you specify a page size (via the
$topquery parameter) that exceeds the maximum page size for that API. The requested page size might be ignored, it might default to the maximum page size for that API, or Microsoft Graph might return an error. - Not all resources or relationships support paging. For example, queries against directoryRole don't support paging. This includes reading role objects themselves and role members.
- When paging against directory resources, any custom request headers (headers that aren't Authorization or Content-Type headers) such as the ConsistencyLevel header aren't included by default in subsequent page requests. If those headers need to be sent on subsequent requests, you must set them explicitly.
- When using the
$count=truequery string when querying against directory resources, the@odata.countproperty is returned only in the first page of the paged result set.
Error handling
Customize Microsoft Graph responses with query parameters
Query parameters help you optimize Microsoft Graph API responses by controlling exactly what data is returned. Instead of retrieving all available properties and data, you can use query parameters to:
- Filter results to get only the records you need
- Select specific properties to reduce response size and improve performance
- Sort and paginate data for better user experiences
- Expand related resources to get connected data in a single request
This article explains how to use OData system query options and other Microsoft Graph query parameters effectively. You learn the syntax, see practical examples, and discover best practices for building efficient queries that enhance your application's performance.
Support for specific query parameters varies between API operations and can differ between the v1.0 and beta endpoints.
[!TIP]
On the beta endpoint, the$prefix is optional. For example, you can usefilterinstead of$filter.
On the v1.0 endpoint, the $ prefix is optional for only a subset of APIs. For simplicity, always include `$` across all versions.OData system query options
A Microsoft Graph API operation might support one or more of the following OData system query options. These query options are compatible with the [OData V4 query language][odata-query] and are supported only in GET operations.
Select the examples to try them in [Graph Explorer][graph-explorer].
| Name | Description | Example |
|---|---|---|
| $count | Returns the total count of matching resources. | [/me/messages?$top=2&$count=true][count-example] |
| $expand | Returns related resources. | [/groups?$expand=members][expand-example] |
| $filter | Filters results (rows). | [/users?$filter=startswith(givenName,'J')][filter-example] |
| $format | Returns results in the specified media format. | [/users?$format=json][format-example] |
| $orderby | Orders results. | [/users?$orderby=displayName desc][orderby-example] |
| $search | Returns results based on search criteria. | [/me/messages?$search=pizza][search-example] |
| $select | Filters properties (columns). | [/users?$select=givenName,surname][select-example] |
| $skip | Skips items in a result set. Also used by some APIs to implement paging and can be used with $top to manually page results. | [/me/messages?$skip=11][skip-example] |
| $top | Sets the page size of results. | [/users?$top=2][top-example] |
To find the OData system query options that an API and its properties support, see the "Properties" table in the resource page and the "Optional query parameters" section of the LIST and GET operations for the API.
Other query parameters
| Name | Description | Example |
|---|---|---|
| $skipToken | Returns the next page of results from result sets that span multiple pages. (Some APIs use $skip instead.) | /users?$skiptoken=X%274453707402000100000017... |
Other OData URL capabilities
The following OData 4.0 capabilities are URL segments, not query parameters.
| Name | Description | Example |
|---|---|---|
| $count | Returns the integer total of the collection. | GET /users/$count <br> GET /groups/{id}/members/$count <br/><br/> Get a count of users |
| $ref | Updates entities membership to a collection. | POST /groups/{id}/members/$ref <br/><br/> Add a member to a group |
| $value | Returns or updates the binary value of an item. | GET /me/photo/$value <br/><br/> Get the photo for a user, group, or team |
| $batch | Combines multiple HTTP requests into a batch request. | POST /$batch <br/><br/> JSON batching |
Encoding query parameters
Percent-encode query parameter values according to RFC 3986. All reserved characters in query strings must be percent-encoded. Many HTTP clients, browsers, and tools (such as the [Graph Explorer][graph-explorer]) handle this encoding automatically. If a query fails, a possible cause is failure to encode the query parameter values appropriately. Sometimes, you need to double-encode values.
[!NOTE]
There's a known issue with encoding ampersand (&) symbols in $search expressions on the v1.0 endpoint. For more information about the issue and the recommended workaround, see Known issue: $search for directory objects fails for encoded ampersand (&) character.For example, an unencoded URL looks like this:
GET https://graph.microsoft.com/v1.0/users?$filter=startswith(givenName, 'J')The properly percent-encoded URL looks like this:
GET https://graph.microsoft.com/v1.0/users?$filter=startswith(givenName%2C+'J')The double-encoded URL looks like this:
GET https://graph.microsoft.com/v1.0/users?$filter=startswith%28givenName%2C%20%27J%27%29Escaping single quotes
For requests that use single quotes, if any parameter values also contain single quotes, they should be double escaped; otherwise, the request fails because of invalid syntax. In the example, the string value let''s meet for lunch? has the single quote escaped.
GET https://graph.microsoft.com/v1.0/me/messages?$filter=subject eq 'let''s meet for lunch?'Count
Use the $count query parameter to get the count of the total number of items in a collection or matching an expression. You can use $count in the following ways:
1. As a query string parameter with the syntax $count=true to include a count of the total number of items in a collection alongside the page of data values returned from Microsoft Graph. For example, users?$count=true. 2. As a URL segment to get only the integer total of the collection. For example, users/$count. 3. In a $filter expression with equality operators to get a collection of data where the filtered property is an empty collection. See Use the $filter query parameter to filter a collection of objects.
[!NOTE]
1. On resources that derive from directoryObject, $count is only supported in an advanced query. See Advanced query capabilities on directory objects.2. Use of $count isn't supported in Azure AD B2C tenants.For example, the following request returns both the contact collection of the current user and the number of items in the contact collection in an @odata.count property.
GET https://graph.microsoft.com/v1.0/me/contacts?$count=trueFor directory objects, that is, resources that derive from directoryObject, the $count query parameter is only supported in advanced queries.
Expand
Many Microsoft Graph resources expose both declared properties of the resource and its relationships with other resources. These relationships are also called reference properties or navigation properties, and they can reference either a single resource or a collection of resources. For example, the mail folders, manager, and direct reports of a user are all exposed as relationships.
You can use the $expand query string parameter to include the expanded resource or collection referenced by a single relationship (navigation property) in your results. For some APIs, only one relationship can be expanded in a single request.
The following example gets root drive information along with the top-level child items in a drive:
GET https://graph.microsoft.com/v1.0/me/drive/root?$expand=childrenWith some resource collections, you can also specify the properties to be returned in the expanded resources by adding a $select parameter. The following example performs the same query as the previous example but uses a `$select` statement to limit the properties returned for the expanded child items to the id and name properties.
GET https://graph.microsoft.com/v1.0/me/drive/root?$expand=children($select=id,name)[!NOTE]
+ Not all relationships and resources support the$expandquery parameter. For example, you can expand the directReports, manager, and memberOf relationships on a user, but you can't expand its events, messages, or photo relationships. Not all resources or relationships support using$selecton expanded items.
>
+ With Microsoft Entra resources that derive from directoryObject, like user and group, $expand typically returns a maximum of 20 items for the expanded relationship and has no @odata.nextLink. For details, see query parameter limitations.>
+ $expand isn't currently supported with advanced queries.Filter
Use the $filter query parameter to get just a subset of a collection. For guidance on using $filter, see Use the $filter query parameter to filter a collection of objects.
Format
Use the $format query parameter to specify the media format of the items returned from Microsoft Graph.
For example, the following request returns the users in the organization in JSON format:
GET https://graph.microsoft.com/v1.0/users?$format=json[!NOTE]
The$formatquery parameter supports many formats (for example,atom,xml, andjson) but results might not be returned in all formats.
OrderBy
Use the $orderby query parameter to specify the sort order of the items returned from Microsoft Graph. The default order is ascending.
For example, the following request returns users in the organization ordered by their display name in ascending order:
GET https://graph.microsoft.com/v1.0/users?$orderby=displayNameSome APIs support sorting by complex type entities. The following request gets messages and sorts them by the address field of the from property, which is of the complex type emailAddress:
GET https://graph.microsoft.com/v1.0/me/messages?$orderby=from/emailAddress/addressTo sort results in ascending or descending order, append either asc or desc to the field name, separated by a space; for example, ?$orderby=name desc (unencoded), ?$orderby=name%20desc (URL encoded). If you don't specify the sort order, ascending order is inferred.
With some APIs, you can order results on multiple properties. For example, the following request orders the messages in the user's Inbox, first by the name of the person who sent it in descending order (Z to A), and then by subject in ascending order (default).
GET https://graph.microsoft.com/v1.0/me/mailFolders/Inbox/messages?$orderby=from/emailAddress/name desc,subject[!NOTE]
When you specify$filter, the service infers a sort order for the results. If you use both$orderbyand$filterto get messages, because the server always infers a sort order for the results of a$filter, you must specify properties in certain ways.
The following example shows a query filtered by the subject and importance properties, and then sorted by the subject, importance, and receivedDateTime properties in descending order.
GET https://graph.microsoft.com/v1.0/me/messages?$filter=Subject eq 'welcome' and importance eq 'normal'&$orderby=subject,importance,receivedDateTime desc[!NOTE]
Combining$orderbyand$filterquery parameters is supported for directory objects. See Advanced query capabilities on directory objects.
Search
Use the $search query parameter to restrict request results to match a search criterion. Its syntax and behavior varies across different resources. For more information, see Use the $search query parameter to match a search criterion.
Select
Use the $select query parameter to return a subset of properties for a resource. With $select, you can specify a subset or a superset of the default properties.
When you make a GET request without using $select to limit the property data, Microsoft Graph includes a @microsoft.graph.tips property that provides a best practice recommendation for using $select similar to the following message:
"@microsoft.graph.tips": "Use $select to choose only the properties your app needs, as this can lead to performance improvements. For example: GET groups?$select=appMetadata,assignedLabels",For example, when getting the messages of the signed-in user, you can specify that only the from and subject properties be returned:
GET https://graph.microsoft.com/v1.0/me/messages?$select=from,subject[!IMPORTANT]
We recommend that you use $select to limit the properties returned by a query to those needed by your app. This is especially true for queries that might potentially return a large result set. Limiting the properties returned in each row reduces network load and improves your app's performance.>
In v1.0, some Microsoft Entra resources that derive from directoryObject, like user and group, return a limited, default subset of properties on reads. For these resources, you must use $select to return properties outside of the default set.Skip
Use the $skip query parameter to set the number of items to skip at the start of a collection. For example, the following request returns events for the user sorted by date created, starting with the 21st event in the collection:
GET https://graph.microsoft.com/v1.0/me/events?$orderby=createdDateTime&$skip=20Some Microsoft Graph APIs, like Outlook Mail and Calendars (message, event, and calendar), use $skip to implement paging. When query results span multiple pages, these APIs return an @odata.nextLink property with a URL that contains a $skip parameter. You can use this URL to return the next page of results. To learn more, see Paging.
Directory objects such as user, group, and application don't support $skip.
SkipToken
Some requests return multiple pages of data, either due to server-side paging or due to using the `$top` parameter to limit the page size of the response. Many Microsoft Graph APIs use the skipToken query parameter to reference subsequent pages of the result. This parameter contains an opaque token that references the next page of results and is returned in the URL provided in the @odata.nextLink property in the response. To learn more, see Paging.
[!NOTE]
If you're using OData Count (adding$count=truein the query string) for queries against directory objects, the@odata.countproperty is present only in the first page.
>
The ConsistencyLevel header required for advanced queries against directory objects isn't included by default in subsequent page requests. It must be set explicitly in subsequent pages.
Top
Use the $top query parameter to specify the number of items to be included in the result.
If more items remain in the result set, the response body contains an @odata.nextLink parameter. This parameter contains a URL that you can use to get the next page of results. To learn more, see Paging.
The minimum value of $top is 1 and the maximum depends on the corresponding API.
For example, the following list messages request returns the first five messages in the user's mailbox:
GET https://graph.microsoft.com/v1.0/me/messages?$top=5[!NOTE]
The ConsistencyLevel header required for advanced queries against directory objects isn't included by default in subsequent page requests. It must be set explicitly in subsequent pages.
Error handling for query parameters
Some requests return an error message if a specified query parameter isn't supported. For example, you can't use $expand on the user/photo relationship.
https://graph.microsoft.com/v1.0/me?$expand=photo{
"error":{
"code":"ExpandNotSupported",
"message":"Expand is not allowed for property 'Photo' according to the entity schema.",
"innerError":{
"request-id":"1653fefd-bc31-484b-bb10-8dc33cb853ec",
"date":"2017-07-31T20:55:01"
}
}
}However, sometimes query parameters specified in a request fail silently. For example, for unsupported query parameters and for unsupported combinations of query parameters. In these cases, examine the data returned by the request to determine whether the query parameters you specified had the desired effect.
Microsoft Graph throttling guidance
Throttling limits the number of concurrent calls to a service to prevent overuse of resources. The Microsoft Graph service implements throttling limits to ensure service availability and reliability.
Throttling limits vary based on the scenario. For example, if you're performing a large volume of writes, the possibility for throttling is higher than if you're only performing reads.
[!NOTE]
Solutions that need to extract a large volume of data from Microsoft Graph should use Microsoft Graph Data Connect instead of the Microsoft Graph REST APIs. Microsoft Graph Data Connect allows organizations to extract Microsoft 365 data in bulk without being subject to throttling limits.
<br/>
What happens when throttling occurs?
When a throttling threshold is exceeded, Microsoft Graph:
- Limits any further requests from that client app for some time.
- Returns HTTP status code 429 Too Many Requests and the requests fail.
- Returns a suggested wait time in the response header of the failed request.
Throttling behavior can depend on the type and number of requests. For example, if you have a high volume of requests, all requests types are throttled. Threshold limits vary based on the request type. Therefore, you could encounter a scenario where writes are throttled but reads are still permitted.
Common throttling scenarios
The most common causes of throttling of clients include:
- A large number of requests across all applications in a tenant.
- A large number of requests from a particular application across all tenants.
Sample response
Whenever the throttling threshold is exceeded, Microsoft Graph responds with a response similar to this one.
HTTP/1.1 429 Too Many Requests
Content-Length: 312
Content-Type: application/json
Retry-After: 10
{
"error": {
"code": "TooManyRequests",
"innerError": {
"code": "429",
"date": "2020-08-18T12:51:51",
"message": "Please retry after",
"request-id": "94fb3b52-452a-4535-a601-69e0a90e3aa2",
"status": "429"
},
"message": "Please retry again later."
}
}Best practices to handle throttling
The following are best practices for handling throttling:
- Reduce the number of operations per request.
- Reduce the frequency of calls.
- Avoid immediate retries, because all requests accrue against your usage limits.
When you implement error handling, use the HTTP error code 429 to detect throttling. The failed response includes the Retry-After response header. Backing off requests using the Retry-After delay is the fastest way to recover from throttling because Microsoft Graph continues to log resource usage while a client is being throttled.
1. Wait the number of seconds specified in the Retry-After header. 2. Retry the request. 3. If the request fails again with a 429 error code, you're still being throttled. Continue to use the recommended Retry-After delay and retry the request until it succeeds.
All the resources and APIs described in the Service-specific limits provide a Retry-After header except where indicated.
For a broader discussion of throttling in the Microsoft Cloud, see Throttling pattern.
[!NOTE]
If no Retry-After header is provided by the response, we recommend implementing an exponential backoff retry policy. You can also implement more advanced patterns when building large-scale applications.>
Microsoft Graph SDKs already implement handlers that rely on the Retry-After header or default to an exponential backoff retry policy.Best practices to avoid throttling
Programming patterns like continuously polling a resource to check for updates and regularly scanning resource collections to check for new or deleted resources are more likely to lead to applications being throttled and degrade overall performances. You should instead use change tracking and change notifications when available.
>[!NOTE] >Best practices for discovering files and detecting changes at scale describes best practices in details.
Throttling and batching
JSON batching allows you to optimize your application by combining multiple requests into a single JSON object. Requests in a batch are evaluated individually against throttling limits and if any request exceeds the limits, it fails with a status code of 429 and an error similar to the preceding sample response. The batch itself succeeds with a status code of 200 (OK). Multiple requests can be throttled in a single batch. You should retry each failed request from the batch using the value provided in the retry-after response header from the JSON content. You might retry all the failed requests in a new batch after the longest retry-after value.
If SDKs retry throttled requests automatically when they aren't batched, throttled requests that were part of a batch aren't retried automatically.
Microsoft Graph API Reference
This reference covers common Graph API patterns, resources, and conventions that agents should know when constructing API calls.
Base URL
https://graph.microsoft.com/{version}/{resource}- beta (default):
https://graph.microsoft.com/beta/ - v1.0 (stable):
https://graph.microsoft.com/v1.0/
Common Resources
Identity & Directory
| Resource | Path | Common Operations |
|---|---|---|
| Current user | /me | GET profile, PATCH update |
| Users | /users | GET list, POST create |
| Specific user | /users/{id or UPN} | GET, PATCH, DELETE |
| Groups | /groups | GET list, POST create |
| Group members | /groups/{id}/members | GET list |
| Service principals | /servicePrincipals | GET list |
| Applications | /applications | GET list, POST create |
| Directory roles | /directoryRoles | GET list |
| Domains | /domains | GET list |
| Organization | /organization | GET details |
| Resource | Path | Common Operations |
|---|---|---|
| Messages | /me/messages | GET list |
| Specific message | /me/messages/{id} | GET, PATCH, DELETE |
| Send mail | /me/sendMail | POST |
| Mail folders | /me/mailFolders | GET list |
| Folder messages | /me/mailFolders/{id}/messages | GET list |
Calendar
| Resource | Path | Common Operations |
|---|---|---|
| Events | /me/events | GET list, POST create |
| Specific event | /me/events/{id} | GET, PATCH, DELETE |
| Calendar view | /me/calendarView | GET (requires startDateTime, endDateTime) |
| Calendars | /me/calendars | GET list |
Teams
| Resource | Path | Common Operations |
|---|---|---|
| Joined teams | /me/joinedTeams | GET list |
| Team details | /teams/{id} | GET |
| Channels | /teams/{id}/channels | GET list, POST create |
| Channel messages | /teams/{id}/channels/{id}/messages | GET list, POST create |
SharePoint & OneDrive
| Resource | Path | Common Operations |
|---|---|---|
| My drive | /me/drive | GET |
| Drive items | /me/drive/root/children | GET list |
| Specific item | /me/drive/items/{id} | GET, PATCH, DELETE |
| SharePoint sites | /sites | GET list |
| Site by path | /sites/{hostname}:/{path} | GET |
| Site lists | /sites/{id}/lists | GET list |
Intune / Device Management
| Resource | Path | Common Operations |
|---|---|---|
| Managed devices | /deviceManagement/managedDevices | GET list |
| Compliance policies | /deviceManagement/deviceCompliancePolicies | GET list |
| Config profiles | /deviceManagement/deviceConfigurations | GET list |
| Mobile apps | /deviceAppManagement/mobileApps | GET list |
Security
| Resource | Path | Common Operations |
|---|---|---|
| Alerts | /security/alerts_v2 | GET list |
| Incidents | /security/incidents | GET list |
| Secure score | /security/secureScores | GET list |
Reports
| Resource | Path | Common Operations |
|---|---|---|
| Sign-in logs | /auditLogs/signIns | GET list |
| Audit logs | /auditLogs/directoryAudits | GET list |
| Usage reports | /reports/getOffice365ActiveUserDetail(period='D7') | GET |
OData Query Parameters
$select — Choose fields
/users?$select=displayName,mail,userPrincipalName$filter — Filter results
/users?$filter=startsWith(displayName,'John')
/users?$filter=department eq 'Engineering'
/me/messages?$filter=isRead eq false
/users?$filter=accountEnabled eq true$top — Limit results
/users?$top=10$orderby — Sort results
/users?$orderby=displayName
/me/messages?$orderby=receivedDateTime desc$expand — Include related resources
/groups/{id}?$expand=members
/me/messages/{id}?$expand=attachments$count — Get count (requires ConsistencyLevel header)
/users/$countUse with header: ConsistencyLevel: eventual
$search — Search (requires ConsistencyLevel header)
/users?$search="displayName:John"Use with header: ConsistencyLevel: eventual
Combining parameters
/users?$select=displayName,mail&$filter=department eq 'Sales'&$top=25&$orderby=displayNameCommon Permission Scopes
| Scope | Description |
|---|---|
User.Read | Read current user profile |
User.Read.All | Read all user profiles |
User.ReadWrite.All | Read and write all user profiles |
Mail.Read | Read user mail |
Mail.ReadWrite | Read and write user mail |
Mail.Send | Send mail as user |
Calendars.Read | Read user calendars |
Calendars.ReadWrite | Read and write user calendars |
Group.Read.All | Read all groups |
Group.ReadWrite.All | Read and write all groups |
Directory.Read.All | Read directory data |
Directory.ReadWrite.All | Read and write directory data |
Files.Read | Read user files |
Files.ReadWrite | Read and write user files |
Sites.Read.All | Read SharePoint sites |
Sites.ReadWrite.All | Read and write SharePoint sites |
Team.ReadBasic.All | Read basic team info |
Channel.ReadBasic.All | Read basic channel info |
ChannelMessage.Read.All | Read channel messages |
DeviceManagementManagedDevices.Read.All | Read managed devices |
SecurityEvents.Read.All | Read security events |
AuditLog.Read.All | Read audit logs |
Pagination
Graph API uses @odata.nextLink for pagination:
{
"@odata.nextLink": "https://graph.microsoft.com/beta/users?$skiptoken=...",
"value": [...]
}To get the next page, make a GET request to the @odata.nextLink URL directly.
Batch Requests
Send multiple requests in a single HTTP call:
POST /beta/$batch
Content-Type: application/json
{
"requests": [
{ "id": "1", "method": "GET", "url": "/me" },
{ "id": "2", "method": "GET", "url": "/me/messages?$top=5" }
]
}Error Response Format
{
"error": {
"code": "Authorization_RequestDenied",
"message": "Insufficient privileges to complete the operation.",
"innerError": {
"date": "2024-01-15T10:00:00",
"request-id": "..."
}
}
}Tips
1. Use $select to reduce response size — only request fields you need 2. Use $top to limit results — avoid fetching thousands of records 3. Check $filter support — not all properties support filtering 4. ConsistencyLevel header is required for $count and $search on directory objects 5. User identifier can be the object ID or userPrincipalName (email) 6. Group identifier must be the object ID 7. Date filters use ISO 8601 format: 2024-01-15T00:00:00Z
SQLite format 3@ .��
�S�#
�
$�r11�tablesamples_fts_configsamples_fts_configCREATE TABLE 'samples_fts_config'(k PRIMARY KEY, v) WITHOUT ROWIDv33�tablesamples_fts_docsizesamples_fts_docsizeCREATE TABLE 'samples_fts_docsize'(id INTEGER PRIMARY KEY, sz BLOB)�++�?tablesamples_fts_idxsamples_fts_idxCREATE TABLE 'samples_fts_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWIDp--�tablesamples_fts_datasamples_fts_dataCREATE TABLE 'samples_fts_data'(id INTEGER PRIMARY KEY, block BLOB)�;##�?tablesamples_ftssamples_ftsCREATE VIRTUAL TABLE samples_fts USING fts5(
intent,
query,
product,
content=samples,
content_rowid=id,
tokenize='porter unicode61'
)�*�+tablesamplessamplesCREATE TABLE samples (
id INTEGER PRIMARY KEY,
intent TEXT NOT NULL,
query TEXT NOT NULL,
product TEXT,
file TEXT
)
H�
�
�
� � �����<� �m�EList all Teams the signed-in user is a member ofGET /v1.0/me/joinedTeams?$select=id,displayName,descriptionteamsteams/list-joined-teams.yaml�Y� !WSearch for SharePoint sites by keywordGET /v1.0/sites?search={keyword}&$select=id,displayName,webUrlsharepointsharepoint/list-sharepoint-sites.yaml�{S]Run an advanced hunting query in Microsoft 365 DefenderPOST /beta/security/runHuntingQuerysecuritysecurity/run-advanced-hunting-query.yaml�[
��oMList all Intune managed devices with their compliance statusGET /v1.0/deviceManagement/managedDevices?$select=id,deviceName,operatingSystem,complianceState,userPrincipalNameintuneintune/list-managed-devices.yaml�[��oGGet the mobile device management authority for the organizationGET /v1.0/organization?$select=id GET /beta/organization/{id from step 1}?$select=mobileDeviceManagementAuthorityintuneintune/get-mdm-authority.yaml� mSaGet Intune device management compliance settingsGET /beta/deviceManagement/settingsintuneintune/get-device-compliance-settings.yaml�6
��OList all license SKUs and their assigned counts in the tenantGET /v1.0/subscribedSkus?$select=skuPartNumber,consumedUnits,prepaidUnitsgeneralgeneral/list-subscribed-skus.yaml�< ��QGet organization details including tenant name and verified domainsGET /v1.0/organization?$select=id,displayName,verifiedDomains,tenantTypegeneralgeneral/get-organization-info.yamlhg9;Send an email on behalf of the signed-in userPOST /v1.0/me/sendMailexchangeexchange/send-mail.yaml�]q�QList unread emails in the signed-in user's mailboxGET /v1.0/me/messages?$filter=isRead eq false&$select=subject,from,receivedDateTime&$orderby=receivedDateTime desc&$top=25exchangeexchange/list-unread-messages.yaml�Ey�[EList all member users in the tenant (excluding guests)GET /beta/users?$select=id,userPrincipalName,displayName,userType&$filter=userType eq 'Member'&$top=999entraentra/list-member-users.yaml�u{�gGet all users assigned to the Global Administrator roleGET /v1.0/roleManagement/directory/roleDefinitions?$filter=displayName eq 'Global Administrator' GET /v1.0/roleManagement/directory/roleAssignmentScheduleInstances?$filter=roleDefinitionId eq '{id from step 1}' and assignmentType eq 'Assigned'&$expand=principalentraentra/list-global-admin-role-assignments.yaml�UgcList all Conditional Access policiesGET /beta/identity/conditionalAccess/policiesentraentra/list-conditional-access-policies.yaml�t�[�9aList all application registrations with their secrets and certificate credentials to find expiring onesGET /v1.0/applications?$select=id,displayName,appId,passwordCredentials,keyCredentialsentraentra/list-apps-with-expiring-secrets.yaml�+�9YSGet the authorization policy for guest access, app registration, and guest invitationsGET /v1.0/policies/authorizationPolicyentraentra/get-authorization-policy.yaml�5�?iQGet the authentication methods policy including method configurations and migration stateGET /beta/policies/authenticationMethodsPolicyentraentra/get-auth-methods-policy.yaml
�����
������~00
125365 999a
ccessdministr vancllnd
pp id licssignmenttyputhenticationmethodspolicior
izationpolicibehalfta ycertif
omplianc
est
nditionalaccessfigur sumedunit
unt
redentidefend
script
tail vicemanagnam
irectori
splaynam
omain
emailntraq
xchanglud pandirfals
ilter
nd
or
rom
gener t
lobaluest
huntid entn
cludtunvit
s read
joinedteamkeycredentiwordlicens
stmailboxnageddevic
ember
ssagthodicrosoftgratobiledevicemanagementauthorname ofnperatingsystem
rderbigan
passwordcredenti
olicist
repaidunit
incipquerireceiveddatetimgistr
ole
assignmentscheduleinstdefinitionidmanag
unhuntingqueris
earchcreturlectndmailt harepointign teku
partnumb
tateu
ep
ubject
scribedsku
teamnanttyp
heiropunreadser principalnamtyp v1
erifi eddomain weburlith
;
"
(
$
Q
-
4
<
��!
$
��
�����������������
��version{
"generated": "2026-06-23T08:54:04Z",
"count": 16,
"samples": [
{
"intent": "Get the authentication methods policy including method configurations and migration state",
"query": "GET /beta/policies/authenticationMethodsPolicy",
"product": "entra",
"file": "entra/get-auth-methods-policy.yaml"
},
{
"intent": "Get the authorization policy for guest access, app registration, and guest invitations",
"query": "GET /v1.0/policies/authorizationPolicy",
"product": "entra",
"file": "entra/get-authorization-policy.yaml"
},
{
"intent": "List all application registrations with their secrets and certificate credentials to find expiring ones",
"query": "GET /v1.0/applications?$select=id,displayName,appId,passwordCredentials,keyCredentials",
"product": "entra",
"file": "entra/list-apps-with-expiring-secrets.yaml"
},
{
"intent": "List all Conditional Access policies",
"query": "GET /beta/identity/conditionalAccess/policies",
"product": "entra",
"file": "entra/list-conditional-access-policies.yaml"
},
{
"intent": "Get all users assigned to the Global Administrator role",
"query": [
"GET /v1.0/roleManagement/directory/roleDefinitions?$filter=displayName eq 'Global Administrator'",
"GET /v1.0/roleManagement/directory/roleAssignmentScheduleInstances?$filter=roleDefinitionId eq '{id from step 1}' and assignmentType eq 'Assigned'\u0026$expand=principal"
],
"product": "entra",
"file": "entra/list-global-admin-role-assignments.yaml"
},
{
"intent": "List all member users in the tenant (excluding guests)",
"query": "GET /beta/users?$select=id,userPrincipalName,displayName,userType\u0026$filter=userType eq 'Member'\u0026$top=999",
"product": "entra",
"file": "entra/list-member-users.yaml"
},
{
"intent": "List unread emails in the signed-in user's mailbox",
"query": "GET /v1.0/me/messages?$filter=isRead eq false\u0026$select=subject,from,receivedDateTime\u0026$orderby=receivedDateTime desc\u0026$top=25",
"product": "exchange",
"file": "exchange/list-unread-messages.yaml"
},
{
"intent": "Send an email on behalf of the signed-in user",
"query": "POST /v1.0/me/sendMail",
"product": "exchange",
"file": "exchange/send-mail.yaml"
},
{
"intent": "Get organization details including tenant name and verified domains",
"query": "GET /v1.0/organization?$select=id,displayName,verifiedDomains,tenantType",
"product": "general",
"file": "general/get-organization-info.yaml"
},
{
"intent": "List all license SKUs and their assigned counts in the tenant",
"query": "GET /v1.0/subscribedSkus?$select=skuPartNumber,consumedUnits,prepaidUnits",
"product": "general",
"file": "general/list-subscribed-skus.yaml"
},
{
"intent": "Get Intune device management compliance settings",
"query": "GET /beta/deviceManagement/settings",
"product": "intune",
"file": "intune/get-device-compliance-settings.yaml"
},
{
"intent": "Get the mobile device management authority for the organization",
"query": [
"GET /v1.0/organization?$select=id",
"GET /beta/organization/{id from step 1}?$select=mobileDeviceManagementAuthority"
],
"product": "intune",
"file": "intune/get-mdm-authority.yaml"
},
{
"intent": "List all Intune managed devices with their compliance status",
"query": "GET /v1.0/deviceManagement/managedDevices?$select=id,deviceName,operatingSystem,complianceState,userPrincipalName",
"product": "intune",
"file": "intune/list-managed-devices.yaml"
},
{
"intent": "Run an advanced hunting query in Microsoft 365 Defender",
"query": "POST /beta/security/runHuntingQuery",
"product": "security",
"file": "security/run-advanced-hunting-query.yaml"
},
{
"intent": "Search for SharePoint sites by keyword",
"query": "GET /v1.0/sites?search={keyword}\u0026$select=id,displayName,webUrl",
"product": "sharepoint",
"file": "sharepoint/list-sharepoint-sites.yaml"
},
{
"intent": "List all Teams the signed-in user is a member of",
"query": "GET /v1.0/me/joinedTeams?$select=id,displayName,description",
"product": "teams",
"file": "teams/list-joined-teams.yaml"
}
]
}
# Launcher script for msgraph CLI.
# Executes the pre-bundled binary for the detected platform.
[CmdletBinding()]
param(
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$Arguments
)
$ErrorActionPreference = 'Stop'
$BinaryName = "msgraph"
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$BinDir = Join-Path $ScriptDir "bin"
function Get-Platform {
$arch = switch ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) {
'X64' { 'amd64' }
'Arm64' { 'arm64' }
default { throw "Unsupported architecture: $_" }
}
return "windows_$arch"
}
# Main logic
$platform = Get-Platform
$binaryPath = Join-Path $BinDir "${BinaryName}_${platform}.exe"
if (-not (Test-Path $binaryPath)) {
Write-Error "Binary not found: $binaryPath`nExpected a pre-bundled binary for platform '$platform'.`nPlease reinstall the skill or download the correct release."
exit 1
}
& $binaryPath @Arguments
exit $LASTEXITCODE
#!/usr/bin/env bash
# Launcher script for msgraph CLI.
# Executes the pre-bundled binary for the detected platform.
set -euo pipefail
BINARY_NAME="msgraph"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BIN_DIR="${SCRIPT_DIR}/bin"
# Detect OS and architecture
detect_platform() {
local os arch
case "$(uname -s)" in
Darwin) os="darwin" ;;
Linux) os="linux" ;;
*) echo "Error: Unsupported OS: $(uname -s)" >&2; exit 1 ;;
esac
case "$(uname -m)" in
x86_64|amd64) arch="amd64" ;;
arm64|aarch64) arch="arm64" ;;
*) echo "Error: Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
esac
echo "${os}_${arch}"
}
# Main logic
main() {
local platform binary_path
platform=$(detect_platform)
binary_path="${BIN_DIR}/${BINARY_NAME}_${platform}"
if [ ! -f "${binary_path}" ]; then
echo "Error: Binary not found: ${binary_path}" >&2
echo "Expected a pre-bundled binary for platform '${platform}'." >&2
echo "Please reinstall the skill or download the correct release." >&2
exit 1
fi
chmod +x "${binary_path}"
exec "${binary_path}" "$@"
}
main "$@"
Related skills
How it compares
Use msgraph when agents must discover correct Graph REST calls at generation time; prefer the official Graph SDK when you already know the exact typed client methods needed.
FAQ
Why do coding agents need the msgraph skill?
msgraph addresses LLM training data that is months old, causing agents to hallucinate outdated Microsoft Graph API URLs and parameters. Community query samples in the skill index provide verified, current Graph REST calls.
How can developers contribute Microsoft Graph samples?
Developers add working Graph API query samples through the msgraph contribution workflow at graph.pm. Accepted samples enter the skill search index so every agent using msgraph can find the correct API call.
Is Msgraph safe to install?
skills.sh reports 1 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.