Microsoft Graph API Integration
Integrates with Microsoft Graph — the unified REST API for Microsoft 365 services — to programmatically manage emails, calendar events, OneDrive and SharePoint files, Teams messages, Excel workbooks, and Entra ID directory data using the official msgraph-sdk for Python.
TL;DR for Code Generation
- Always authenticate via
Azure.Identityusing eitherClientSecretCredential(daemon apps) orDeviceCodeCredential(user delegation) - Use
msgraph-sdkv1.57+ with the synchronous or asyncGraphServiceClient - Prefer
.select(),.filter(), and.top()to minimize payload size - Handle
APIErrorwith a 3-attempt retry + exponential backoff - Batch requests for bulk operations (max 20 per batch)
- Use
@odata.nextLinkpagination for large result sets
When to Use
Use this skill when:
- Sending or reading emails via Microsoft 365 mailboxes
- Creating, updating, or querying calendar events and meeting invites
- Uploading, downloading, or managing files in OneDrive or SharePoint
- Sending and receiving Teams channel messages
- Reading or writing Excel workbook ranges
- Managing users, groups, and directory entries in Entra ID
- Automating approval workflows across Microsoft 365
When NOT to Use
- Accessing on-premises Exchange or SharePoint (use the respective on-prem APIs)
- Graph API permissions that require admin consent without a proper review
- Sending high-volume bulk email (use Exchange Online SendMail or SMTP relay instead)
- Replacing a dedicated SIEM tool for security audit log analysis (use Microsoft Graph Security API separately)
Core Workflow
Register Application in Entra ID — Create an app registration and configure API permissions (delegated or application). Checkpoint: Verify the app has the correct
Microsoft.Graphpermission scopes granted and consented.Choose Authentication Strategy — For daemon/server apps, use
ClientSecretCredentialwith application permissions. For user-delegated access, useDeviceCodeCredentialorInteractiveBrowserCredential. Checkpoint: Validate the token acquires without interactive prompts in production.Initialize GraphServiceClient — Instantiate the client with the credential and the default scope
["https://graph.microsoft.com/.default"]. Checkpoint: CallGET /v1.0/me(or equivalent) to confirm connectivity.Construct the Request — Use the fluent builder pattern:
client.users.by_user_id(id).messages.get(). Apply.select(),.filter(),.top(), and.orderby()query parameters. Checkpoint: Verify the OData query compiles and returns expected fields.Handle Pagination — Use
valueand@odata.nextLinkfrom the response page. Formsgraph-sdk, call.next_page()on the response object if more results exist. Checkpoint: Confirm at least one page was retrieved with the expected schema.Process the Response — Iterate over the returned collection or single object. Wrap all calls in
try/except APIErrorto catch authentication, throttling, and permission errors. Checkpoint: Parse the error body forinnerError.codeto differentiate transient vs. permanent failures.
Implementation Patterns
Pattern 1: Send an Email
import os
from azure.identity import ClientSecretCredential
from msgraph import GraphServiceClient
from msgraph.generated.users.item.send_mail.send_mail_post_request_body import SendMailPostRequestBody
from msgraph.generated.models.message import Message
from msgraph.generated.models.recipient import Recipient
from msgraph.generated.models.email_address import EmailAddress
credential = ClientSecretCredential(
tenant_id=os.environ["AZURE_TENANT_ID"],
client_id=os.environ["AZURE_CLIENT_ID"],
client_secret=os.environ["AZURE_CLIENT_SECRET"],
)
client = GraphServiceClient(credential)
message = Message(
subject="Deployment Complete",
to_recipients=[
Recipient(email_address=EmailAddress(address="team@example.com"))
],
body=Body(content="The v2.5.0 deployment finished successfully.", content_type=Text),
)
body = SendMailPostRequestBody(message=message)
client.users.by_user_id("admin@example.com").send_mail.post(body)
Pattern 2: List Calendar Events (with Filter and Select)
from datetime import datetime, timedelta
today = datetime.utcnow().isoformat() + "Z"
next_week = (datetime.utcnow() + timedelta(days=7)).isoformat() + "Z"
query_params = client.me.calendar.events.get_query_parameter.Filter(
f"start/dateTime ge '{today}' and start/dateTime le '{next_week}'"
)
query_params.select = ["subject", "start", "end", "location"]
query_params.top = 25
events_page = client.me.calendar.events.get(query_parameters=query_params)
for event in events_page.value:
print(f"{event.subject} — {event.start.date_time}")
Pattern 3: Upload File to OneDrive (with Conflict Handling)
from msgraph.generated.models.drive_item import DriveItem
from msgraph.generated.models.upload_session import UploadSession
# Large file upload via upload session
file_path = "report_q1.pdf"
file_size = os.path.getsize(file_path)
upload_session = client.drives.by_drive_id("drive-id").items.by_drive_item_id("parent-id").create_upload_session.post(
body=DriveItem(name="report_q1.pdf")
)
with open(file_path, "rb") as f:
for chunk_start in range(0, file_size, 3_276_800):
chunk_end = min(chunk_start + 3_276_800, file_size)
chunk = f.read(3_276_800)
client.drives.by_drive_id("drive-id").items.by_drive_item_id("parent-id").upload_session(upload_session.upload_url).put(chunk)
Pattern 4: BAD vs GOOD — Error Handling
# ❌ BAD — no error handling, uses hardcoded IDs
client.users.by_user_id("admin@example.com").messages.by_message_id("msg123").delete()
# ✅ GOOD — resilient error handling with retry hint
from msgraph.core.models import APIError
def delete_message(user_id: str, message_id: str) -> bool:
"""Delete a message and return True on success."""
try:
client.users.by_user_id(user_id).messages.by_message_id(message_id).delete()
return True
except APIError as e:
if e.response.status_code == 404:
print(f"Message {message_id} not found — may already be deleted.")
return False
if e.response.status_code == 429:
retry_after = int(e.response.headers.get("Retry-After", "5"))
print(f"Throttled. Retry after {retry_after}s.")
raise
print(f"API error deleting message: {e}")
raise
Constraints
MUST DO
- Use environment variables or Azure Key Vault for credentials — never commit tokens
- Always specify
selectto limit returned fields; Graph API returns full objects by default - Handle
429 Too Many RequestswithRetry-Afterbackoff - Use
filterserver-side for date ranges instead of client-side filtering - Prefer batch requests for bulk operations (max 20 sub-requests per batch)
MUST NOT DO
- Use the
/betaendpoint in production without testing for breaking changes - Leak
@odata.nextLinkpagination — always iterate through all pages - Graph API for real-time notifications (use webhooks/subscriptions instead of polling)
- Hardcode tenant IDs or client secrets in source code
Output Template
Every integration function should expose:
- Initialization — Credential loading and client construction
- Operation — Specific Graph API call with typed parameters
- Response Handling — Pagination loop or single-object return
- Error Boundary —
try/except APIErrorwith actionable messaging
Related Skills
| Skill | Purpose | |