AgentSkillsCN

microsoft-graph-api

全面梳理 Microsoft Graph API,为 M365 服务集成提供权威参考。

SKILL.md
--- frontmatter
name: "microsoft-graph-api"
description: "Comprehensive Microsoft Graph API reference for M365 service integration"

Microsoft Graph API Skill

Comprehensive reference for Microsoft Graph API integration including endpoints, authentication, rate limiting, and best practices.

⚠️ Staleness Warning

Microsoft Graph APIs evolve frequently. Permissions, endpoints, and authentication flows may change.

Refresh triggers:

  • Microsoft Graph API version updates
  • MSAL library major releases
  • Azure AD → Microsoft Entra ID migration
  • New Graph scopes or permissions

Last validated: February 2026 (Graph v1.0, MSAL 2.x)

Check current state: Graph Explorer, Graph API Reference


API Quick Reference

Base URLs

EnvironmentURL
Production (v1.0)https://graph.microsoft.com/v1.0
Betahttps://graph.microsoft.com/beta
China (21Vianet)https://microsoftgraph.chinacloudapi.cn/v1.0

Best Practice: Use v1.0 for production. Beta endpoints can change without notice.

Authentication

MethodHeaderUse Case
Delegated (user)Authorization: Bearer {token}Interactive apps — acts on behalf of signed-in user
ApplicationAuthorization: Bearer {token}Background services — acts as the app itself

Token Acquisition (VS Code Extension):

typescript
// Progressive scope acquisition — request minimal scopes initially
const INITIAL_SCOPES = ['User.Read'];
const FULL_SCOPES = [
    'User.Read',
    'Calendars.Read',
    'Mail.Read',
    'Presence.Read',
    'People.Read',
    'Group.Read.All'
];

async function getGraphToken(): Promise<string | null> {
    const session = await vscode.authentication.getSession(
        'microsoft',
        FULL_SCOPES,
        { createIfNone: false }
    );
    return session?.accessToken ?? null;
}

Permissions (Scopes) Reference

Common Delegated Scopes

ScopePurpose
User.ReadRead signed-in user profile
User.ReadBasic.AllRead basic profile of all users
Mail.ReadRead user mail
Mail.SendSend mail as the user
Calendars.ReadRead user calendar events
Calendars.ReadWriteCreate/update calendar events
Presence.ReadRead user presence status
People.ReadRead user's relevant people
Group.Read.AllRead all groups
Sites.Read.AllRead SharePoint sites
Files.Read.AllRead all files user can access
Tasks.ReadRead user's tasks (To Do)
Tasks.ReadWriteCreate/update tasks (Planner/To Do)

Common Application Scopes

ScopePurpose
User.Read.AllRead all user profiles (app-only)
Group.Read.AllRead all groups (app-only)
Mail.ReadRead all users' mail (requires admin consent)
AuditLog.Read.AllRead audit logs
Reports.Read.AllRead M365 usage reports
ServiceHealth.Read.AllRead M365 service health

Principle of Least Privilege: Request only the scopes your app actually needs. Start with User.Read and add incrementally.


Key Endpoints by Service

Users

OperationMethodEndpoint
Get current userGET/me
Get user by ID/UPNGET/users/{id-or-upn}
List usersGET/users
Get user photoGET/me/photo/$value
Get managerGET/me/manager
Get direct reportsGET/me/directReports

Mail

OperationMethodEndpoint
List messagesGET/me/messages
Get messageGET/me/messages/{message-id}
Send mailPOST/me/sendMail
List mail foldersGET/me/mailFolders

Calendar

OperationMethodEndpoint
List eventsGET/me/calendar/events
Calendar viewGET/me/calendarView?startDateTime={start}&endDateTime={end}
Create eventPOST/me/calendar/events
Get eventGET/me/events/{event-id}

Presence

OperationMethodEndpoint
Get my presenceGET/me/presence
Get user presenceGET/users/{id}/presence
Get presence for multiplePOST/communications/getPresencesByUserId

People & Insights

OperationMethodEndpoint
List relevant peopleGET/me/people
Get trending docsGET/me/insights/trending
Get used docsGET/me/insights/used
Get shared docsGET/me/insights/shared

SharePoint & OneDrive

OperationMethodEndpoint
List sitesGET/sites
Get site by pathGET/sites/{hostname}:/{server-relative-path}
List drivesGET/me/drives
List drive itemsGET/me/drive/root/children
Search filesGET/me/drive/root/search(q='{query}')
Upload filePUT/me/drive/items/{parent-id}:/{filename}:/content

Planner (Task Management)

OperationMethodEndpoint
List plans for groupGET/groups/{group-id}/planner/plans
List tasks in planGET/planner/plans/{plan-id}/tasks
Create taskPOST/planner/tasks
Update taskPATCH/planner/tasks/{task-id}
Get user tasksGET/me/planner/tasks

Note: Planner only supports delegated permissions. Application permissions are not available.

To Do

OperationMethodEndpoint
List task listsGET/me/todo/lists
Create task listPOST/me/todo/lists
List tasksGET/me/todo/lists/{list-id}/tasks
Create taskPOST/me/todo/lists/{list-id}/tasks
Update taskPATCH/me/todo/lists/{list-id}/tasks/{task-id}

Groups & Teams

OperationMethodEndpoint
List groupsGET/groups
Get groupGET/groups/{group-id}
List group membersGET/groups/{group-id}/members
List joined teamsGET/me/joinedTeams
Get team channelsGET/teams/{team-id}/channels
Post channel messagePOST/teams/{team-id}/channels/{channel-id}/messages

Service Health & Communications (FishbowlGovernance pattern)

OperationMethodEndpointScope
List health overviewsGET/admin/serviceAnnouncement/healthOverviewsServiceHealth.Read.All
List active issuesGET/admin/serviceAnnouncement/issuesServiceHealth.Read.All
Get issue detailGET/admin/serviceAnnouncement/issues/{id}ServiceHealth.Read.All
List message centerGET/admin/serviceAnnouncement/messagesServiceMessage.Read.All

Rate limit: 1,500 requests / 10 minutes

Audit Logs (FishbowlGovernance pattern)

OperationMethodEndpointScope
List directory auditsGET/auditLogs/directoryAuditsAuditLog.Read.All
List sign-in logsGET/auditLogs/signInsAuditLog.Read.All
List provisioning logsGET/auditLogs/provisioningAuditLog.Read.All

Rate limit: Security endpoints = 150 requests / 10 minutes

Sensitivity Labels (Information Protection)

OperationMethodEndpointScope
List labelsGET/informationProtection/policy/labelsInformationProtectionPolicy.Read
Evaluate classificationPOST/informationProtection/policy/labels/evaluateClassificationResultsInformationProtectionPolicy.Read
Extract labelPOST/informationProtection/policy/labels/extractLabelInformationProtectionPolicy.Read

Critical Patterns

Custom Error Types

typescript
export class GraphRateLimitError extends Error {
  public readonly retryAfter: number;

  constructor(retryAfter: number, message = '') {
    super(`Rate limited. Retry after ${retryAfter}s. ${message}`);
    this.name = 'GraphRateLimitError';
    this.retryAfter = retryAfter;
  }
}

export class GraphApiError extends Error {
  public readonly statusCode: number;
  public readonly errorCode: string;

  constructor(statusCode: number, errorCode: string, message: string) {
    super(`Graph API ${statusCode} (${errorCode}): ${message}`);
    this.name = 'GraphApiError';
    this.statusCode = statusCode;
    this.errorCode = errorCode;
  }
}

API Client Pattern (with retry + timeout)

typescript
const GRAPH_ENDPOINT = 'https://graph.microsoft.com/v1.0';
const DEFAULT_TIMEOUT_MS = 30000;
const DEFAULT_MAX_RETRIES = 3;

async function graphRequest<T>(
  method: 'GET' | 'POST' | 'PATCH' | 'DELETE',
  endpoint: string,
  options: RequestInit = {},
  config: { timeoutMs?: number; maxRetries?: number; throwOnError?: boolean } = {}
): Promise<T | null> {
    const token = await getGraphToken();
    if (!token) return null;

    const { timeoutMs = DEFAULT_TIMEOUT_MS, maxRetries = DEFAULT_MAX_RETRIES, throwOnError = false } = config;

    for (let attempt = 0; attempt <= maxRetries; attempt++) {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

        try {
            const response = await fetch(`${GRAPH_ENDPOINT}${endpoint}`, {
                method,
                ...options,
                signal: controller.signal,
                headers: { 'Authorization': `Bearer ${token}`, ...options.headers }
            });
            clearTimeout(timeoutId);

            // Handle 429 rate limiting
            if (response.status === 429) {
                const retryAfter = parseInt(response.headers.get('Retry-After') || '10');
                if (attempt < maxRetries) {
                    await new Promise(r => setTimeout(r, retryAfter * 1000));
                    continue;
                }
                if (throwOnError) throw new GraphRateLimitError(retryAfter);
                return null;
            }

            // Handle 5xx with exponential backoff
            if (response.status >= 500 && attempt < maxRetries) {
                await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
                continue;
            }

            if (!response.ok) {
                if (throwOnError) {
                    const err = await response.json().catch(() => ({}));
                    throw new GraphApiError(response.status, err?.error?.code || 'Unknown', err?.error?.message || response.statusText);
                }
                return null;
            }

            return response.json();
        } catch (error) {
            clearTimeout(timeoutId);
            if (error instanceof Error && error.name === 'AbortError' && attempt < maxRetries) {
                await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
                continue;
            }
            throw error;
        }
    }
    return null;
}

OData Query Parameters

Graph supports standard OData query parameters:

ParameterExamplePurpose
$select?$select=id,displayName,mailReturn only specified properties
$filter?$filter=department eq 'Engineering'Filter results server-side
$orderby?$orderby=displayNameSort results
$top?$top=10Limit result count
$skip?$skip=20Skip N results (not all APIs)
$expand?$expand=managerInclude related resources inline
$count?$count=trueInclude total count in response
$search?$search="displayName:Fabio"Full-text search

Combining parameters:

http
GET /users?$select=id,displayName,department&$filter=department eq 'Analytics'&$top=25&$orderby=displayName

Not all endpoints support all parameters. Check specific endpoint docs.

Pagination

Graph uses @odata.nextLink for pagination:

typescript
async function graphFetchAll<T>(path: string): Promise<T[]> {
    const token = await getGraphToken();
    if (!token) return [];
    
    const results: T[] = [];
    let url: string | null = `${GRAPH_ENDPOINT}${path}`;

    while (url) {
        const response = await fetch(url, {
            headers: { 'Authorization': `Bearer ${token}` }
        });
        const data = await response.json();
        results.push(...(data.value || []));
        url = data['@odata.nextLink'] || null;
    }

    return results;
}

JSON Batching

Combine up to 20 requests in a single HTTP call:

typescript
interface BatchRequest {
    id: string;
    method: 'GET' | 'POST' | 'PATCH' | 'DELETE';
    url: string;
    body?: unknown;
}

async function graphBatch<T>(requests: BatchRequest[]): Promise<Map<string, T>> {
    if (requests.length > 20) {
        console.warn('Batch limit is 20, use graphBatchAll() for unlimited');
        requests = requests.slice(0, 20);
    }

    const response = await graphPost<{ responses: Array<{ id: string; status: number; body: T }> }>(
        '/$batch',
        { requests }
    );

    const results = new Map<string, T>();
    for (const resp of response?.responses || []) {
        if (resp.status >= 200 && resp.status < 300) {
            results.set(resp.id, resp.body);
        }
    }
    return results;
}

// Auto-chunk unlimited requests into batches of 20
async function graphBatchAll<T>(requests: BatchRequest[]): Promise<Map<string, T>> {
    const allResults = new Map<string, T>();
    for (let i = 0; i < requests.length; i += 20) {
        const chunk = requests.slice(i, i + 20);
        const chunkResults = await graphBatch<T>(chunk);
        for (const [id, body] of chunkResults) {
            allResults.set(id, body);
        }
    }
    return allResults;
}

Helper: Build Batch Request

typescript
function buildBatchRequest(
    method: 'GET' | 'POST' | 'PATCH' | 'DELETE',
    url: string,
    body?: unknown,
    requestId?: string
): BatchRequest {
    return {
        id: requestId || Math.random().toString(36).substring(2, 10),
        method,
        url,
        body,
    };
}

Rate Limits & Throttling

Service-Specific Limits

ServicePer App per TenantNotes
Outlook (Mail/Calendar)10,000 requests / 10 minStandard throttling
TeamsVaries by endpointChannel messages more restrictive
SharePoint/OneDriveBased on concurrent callsUse batching
Directory (Users/Groups)10,000 requests / 10 minStandard throttling
Service Health1,500 requests / 10 minLower limit - cache results
Security (Alerts/Incidents)150 requests / 10 minMuch lower - batch carefully
Audit Logs1,000 requests / 10 minLower limit - paginate wisely

Throttled Response

http
HTTP/1.1 429 Too Many Requests
Retry-After: 30

Best Practices for Avoiding Throttling

  1. Use $select to request only needed properties
  2. Use $filter server-side instead of fetching all and filtering locally
  3. Use JSON batching to reduce request count
  4. Implement exponential backoff with jitter
  5. Cache responses where data doesn't change frequently

Token Lifetime

TokenDefault Lifetime
Access token60-90 minutes
Refresh tokenUp to 90 days
ID token60 minutes

Always use MSAL rather than raw OAuth. MSAL handles caching, refresh, and retry automatically.


SDKs & Client Libraries

LanguagePackageNotes
TypeScript/JS@microsoft/microsoft-graph-clientOfficial SDK
Pythonmsgraph-sdk-pythonOfficial SDK
PowerShellMicrosoft.GraphInstall-Module Microsoft.Graph
.NETMicrosoft.GraphNuGet package

Alex-Specific Integration Points

FeatureEndpointAlex Usage
Calendar context/me/calendarViewMeeting prep, scheduling awareness
Email context/me/messagesCommunication context
Send email/me/sendMailProactive notifications, weekly reports
Presence/me/presenceAvailability in status
People/me/peopleOrg context, relevant contacts
OneDrive/me/driveKnowledge file sync
OneDrive upload/me/drive/root:/{path}:/contentFile archival, exports
Service Health/admin/serviceAnnouncement/healthOverviewsAlex-aware service status
Service Issues/admin/serviceAnnouncement/issuesProactive troubleshooting
Sensitivity Labels/me/informationProtection/sensitivityLabelsDocument classification

Synapses

code
→ [enterprise-integration skill] AUTH_PATTERNS_AND_SCOPES (strong, bidirectional)
→ [vscode-extension-patterns skill] VSCODE_AUTH_SESSION_API (strong, outbound)
→ [alex-core] ENTERPRISE_MODE_GATING (strong, inbound)
→ [localization skill] USER_PREFERRED_LANGUAGE_FROM_GRAPH (moderate, outbound)
→ [GI-heir-promotion-pattern-graph-api-2026-02-12] PROMOTION_CASE_STUDY (strong, origin)
→ [FishbowlGovernance DK-MICROSOFT-GRAPH.md] HEIR_SOURCE_KNOWLEDGE (strong, inbound)
→ [error-handling-patterns] CUSTOM_ERROR_TYPES (moderate, outbound)
→ [api-design patterns] BATCH_AUTO_CHUNKING_PATTERN (strong, outbound)

Session 2026-02-12: Heir Promotion

  • Promoted from FishbowlGovernance heir's production Graph integration
  • Added: Service Health, Email, OneDrive modules
  • Added: graphBatchAll() auto-chunking pattern
  • Blocker discovered: Admin consent required for Microsoft tenants

References