Python client library for Microsoft 365 and Microsoft Graph APIs.
Covers SharePoint REST API v1, Microsoft Graph (Outlook, OneDrive, Teams, OneNote, Planner, and more), and supports all modern Azure AD authentication flows.
Python 3.8+ required.
- Installation
- Which client do I need?
- Authentication
- SharePoint — ClientContext
- Microsoft Graph — GraphClient
- Dependencies
pip install office365-rest-python-clientWith uv:
uv pip install office365-rest-python-clientLatest from GitHub (includes unreleased changes):
pip install git+https://github.com/vgrem/office365-rest-python-client.gitClientContext |
GraphClient |
|
|---|---|---|
| Target API | SharePoint REST API v1 | Microsoft Graph API |
| Use for | SharePoint lists, files, folders, search, site admin, permissions | Outlook, OneDrive, Teams, OneNote, Planner, Users, Groups |
| SharePoint via Graph? | — | Partial — use ClientContext for full SharePoint fidelity |
| Docs | SharePoint REST API | Microsoft Graph |
All modern auth flows use Azure AD via the MSAL library. Legacy flows (ACS, SAML) are fully retired as of April/May 2026.
Two clients, different auth capabilities:
ClientContext |
GraphClient |
|
|---|---|---|
| Target | SharePoint REST API v1 | Microsoft Graph API |
| Resource | {tenant}.sharepoint.com |
graph.microsoft.com |
| Flow | Method | User/App | MFA | Status | Docs |
|---|---|---|---|---|---|
| Certificate (Azure AD) | with_client_certificate(tenant, client_id, thumbprint, cert_path) |
App-only | — | ✅ Recommended | Docs |
| Username & password (MSAL ROPC) | with_username_and_password(tenant, client_id, username, password) |
Delegated | ❌ | ✅ Supported | Docs |
| Interactive (browser) | with_interactive(tenant, client_id) |
Delegated | ✅ | ✅ Supported | Docs |
| Device code | with_device_flow(tenant, client_id) |
Delegated | ✅ | ✅ Supported | Docs |
| NTLM (on-prem only) | with_user_credentials(username, password) |
Delegated | — | ✅ On-prem only | Example |
| Client secret (Azure AD) | with_client_secret(tenant, client_id, secret) |
App-only | — | ❌ Not documented | Microsoft's own migration guidance confirms: replace legacy ACS auth with OAuth2 client credentials flow using certificates, not client secrets, for SharePoint access. |
with_user_credentials(username, password) |
Delegated | ❌ | 🚫 Retired May 2026 | MC1184649 | |
with_credentials(ClientCredential(...)) |
App-only | — | 🚫 Retired Apr 2026 | Notice |
| Flow | Method | User/App | MFA | Status | Docs |
|---|---|---|---|---|---|
| Client secret | with_client_secret(client_id, secret) |
App-only | — | ✅ Recommended | Docs |
| Certificate | with_client_certificate(client_id, thumbprint, key) |
App-only | — | ✅ Recommended | Docs |
| Interactive | with_token_interactive(client_id) |
Delegated | ✅ | ✅ Supported | Docs |
| Username & password | with_username_and_password(client_id, username, password) |
Delegated | ❌ | ✅ Supported | Docs |
| Custom token | GraphClient(acquire_token_func) |
Both | ✅ | ✅ Supported | — |
For national and sovereign clouds, pass the environment parameter:
from office365.azure_env import AzureEnvironment
from office365.sharepoint.client_context import ClientContext
ctx = ClientContext("{site_url}", environment=AzureEnvironment.USGovernmentHigh)\
.with_client_certificate(...)| Environment | Constant |
|---|---|
| Global (default) | AzureEnvironment.Global |
| US Government GCC | AzureEnvironment.USGovernment |
| US Government GCC High | AzureEnvironment.USGovernmentHigh |
| US Government DoD | AzureEnvironment.USGovernmentDoD |
| China (21Vianet) | AzureEnvironment.China |
| Germany (legacy) | AzureEnvironment.Germany |
from office365.sharepoint.client_context import ClientContext
ctx = ClientContext("{site_url}").with_client_certificate(
tenant="{tenant}", client_id="{client_id}",
thumbprint="{thumbprint}", cert_path="./cert.pem"
)
web = ctx.web.get().execute_query()
print(f"Site title: {web.title}")# Get all items (handles 5000+ row threshold automatically)
items = ctx.web.lists.get_by_title("Orders").items.get_all().execute_query()
for item in items:
print(item.properties["Title"])
# Create item
target_list = ctx.web.lists.get_by_title("Tasks")
item = target_list.add_item({"Title": "New task", "Status": "Active"}).execute_query()
# Bulk create — auto-batches in chunks of 100
for row in data:
target_list.add_item({"Title": row["name"]})
ctx.execute_batch()
# Filter, select, expand
items = ctx.web.lists.get_by_title("Projects")\
.items\
.filter("Status eq 'Active'")\
.select(["Title", "Author/Title"])\
.expand(["Author"])\
.get_all().execute_query()# Upload file
with open("report.pdf", "rb") as f:
folder = ctx.web.get_folder_by_server_relative_url("/sites/mysite/Shared Documents")
file = folder.upload_file("report.pdf", f).execute_query()
# Download file
with open("report.pdf", "wb") as f:
ctx.web.get_file_by_server_relative_path("/sites/mysite/Shared Documents/report.pdf")\
.download(f).execute_query()
# Large file upload (chunked)
folder.files.create_upload_session(
local_path, chunk_size=10*1024*1024,
chunk_uploaded=lambda offset: print(f"{offset} bytes uploaded")
).execute_query()
# Download folder as zip
with open("archive.zip", "wb") as f:
folder.download_folder(f).execute_query()
# Create nested folders
base = ctx.web.get_folder_by_server_relative_url("/sites/mysite/Shared Documents")
sub = base.add("Projects").execute_query()
sub.add("2025").execute_query()All file examples | All folder examples
from office365.sharepoint.search.request import SearchRequest
from office365.sharepoint.search.service import SearchService
search = SearchService(ctx)
request = SearchRequest("IsDocument:1", RowLimit=50, StartRow=0)
result = search.post_query(request).execute_query()
rows = result.value.PrimaryQueryResult.RelevantResults.Table.Rows
for row in rows:
print(row)from office365.sharepoint.roles.type import RoleType
# Break inheritance and grant access to a user
role_def = ctx.web.role_definitions.get_by_type(RoleType.Reader)
user = ctx.web.ensure_user("i:0#.f|membership|user@company.com")
item = ctx.web.lists.get_by_title("Confidential").items.get_by_id(1)
item.break_role_inheritance(copy_role_assignments=False)
item.add_role_assignment(user, role_def)
ctx.execute_query()from office365.sharepoint.tenant.administration.tenant import Tenant
tenant = Tenant(ctx)
# List all sites
sites = tenant.get_site_properties_from_sharepoint_by_filters().execute_query()
for site in sites:
print(site.url)
# Create a site
tenant.create_site({"Url": "https://tenant.sharepoint.com/sites/newsite", "Title": "New Site"}).execute_query()from office365.graph_client import GraphClient
client = GraphClient(tenant="{tenant}").with_client_secret(
client_id="{client_id}", client_secret="{client_secret}"
)
me = client.me.get().execute_query()
print(f"Signed in as: {me.user_principal_name}")# Send email
client.me.send_mail(
subject="Hello",
body="Message body",
to_recipients=["user@company.com"]
).execute_query()
# List messages
messages = client.me.messages.top(10).get().execute_query()
for msg in messages:
print(msg.subject)
# Create calendar event
from office365.outlook.calendar.events.event import Event
event = client.me.calendar.events.add(
subject="Team standup",
start="2025-06-01T09:00:00",
end="2025-06-01T09:30:00"
).execute_query()# List drives
drives = client.drives.get().execute_query()
# Upload file to OneDrive
with open("report.xlsx", "rb") as f:
client.me.drive.root.upload("report.xlsx", f).execute_query()
# Download file
with open("report.xlsx", "wb") as local_file:
client.me.drive.root.get_by_path("Documents/report.xlsx")\
.download(local_file).execute_query()# List all teams
teams = client.groups.get().execute_query()
# Send channel message
client.teams["{team_id}"].channels["{channel_id}"]\
.messages.add(body="Hello team!").execute_query()
# Create a team
new_team = client.groups["{group_id}"].add_team().execute_query()# Create a page
with open("MyPage.html", "rb") as f:
page = client.me.onenote.pages.add(presentation_file=f).execute_query()# Create a task
task = client.planner.tasks.add(
title="Review PR",
planId="{plan_id}"
).execute_query()# List users
users = client.users.top(100).get().execute_query()
# Create group
group = client.groups.add(
display_name="Engineering",
mail_nickname="engineering",
mail_enabled=False,
security_enabled=True
).execute_query()Installed automatically:
| Package | Purpose |
|---|---|
| requests | HTTP transport |
| msal | Azure AD authentication |
Optional:
pip install office365-rest-python-client[pandas] # to_dataframe() on collectionsPRs welcome. See issues for things to work on.