Skip to content

Repository files navigation

office365-rest-python-client

A Python client library for Microsoft 365, the SharePoint REST API and Microsoft Graph.

Downloads PyPI PyPI pyversions License: MIT Sponsor

Python 3.8+ · typed models · fluent queries · deferred execution · 700+ runnable examples

  • Two clients, one library — ClientContext speaks the SharePoint REST API; GraphClient speaks Microsoft Graph.
  • Fluent and deferred — chain .select(), .filter(), .expand(), .top(), .order_by() and only hit the wire when you call execute_query().
  • Typed models — every entity is a real Python class (List, File, Team, Message, …), not a dictionary.
  • Copy-paste examples — a runnable script for every service and scenario under examples/.

New here? Pick a client in Choose your client, then jump straight to a runnable snippet in Examples by product.

Table of contents

Choose your client

The library ships two clients. Pick the one that matches the API you need.

ClientContext GraphClient
Talks to SharePoint REST API (/_api) Microsoft Graph (graph.microsoft.com)
Entry point ClientContext("https://contoso.sharepoint.com/sites/team") GraphClient(tenant="contoso.onmicrosoft.com")
Best for SharePoint lists, items, files, folders, search, permissions, site and tenant administration, taxonomy, webhooks Outlook, OneDrive, Teams, OneNote, Planner, To Do, Entra ID, Intune, Purview, Bookings, reports, security…
SharePoint Full fidelity Partial — prefer ClientContext for SharePoint-only work
Typed models List, ListItem, File, Folder, Web, Site, User … DriveItem, Team, Message, Event, User, Group, PlannerTask …
Auth certificate, interactive, device flow, cookies, NTLM (on-prem) client secret, certificate, interactive, device flow, ROPC
Examples examples/sharepoint/ examples/ by product
API reference SharePoint REST Microsoft Graph

Rule of thumb

  • SharePoint lists, files, site or tenant administration → ClientContext
  • Teams, Outlook, OneDrive, Entra ID, Intune, Purview, reports → GraphClient
  • OneDrive files work with either client; GraphClient is the modern, cross-service path.

Installation

pip install office365-rest-python-client

With uv:

uv add office365-rest-python-client

Directly from source:

pip install git+https://github.com/vgrem/office365-rest-python-client.git

Authentication

Modern flows authenticate through Microsoft Entra ID using MSAL. Both clients support delegated (user) and app-only (application) access.

Important

SharePoint ACS app-only access was retired in April 2026 and SAML-based authentication in May 2026. Use the certificate, interactive or device-code flows below instead — see examples/sharepoint/auth/modern/ and the migration script migrate_from_saml_to_cert.py.

ClientContext (SharePoint)

Flow Access Method
Certificate App-only with_client_certificate(tenant, client_id, thumbprint, cert_path=...)
Interactive Delegated (MFA) with_interactive(tenant, client_id)
Device code Delegated (MFA) with_device_flow(tenant, client_id)
Username / password Delegated with_username_and_password(tenant, client_id, username, password)
Custom token Either with_access_token(token_func)
Browser cookies Delegated with_cookies(cookie_source)
NTLM On-premises ClientContext(url, allow_ntlm=True).with_user_credentials(username, password)
ACS / SAML On-premises (legacy) legacy app-only · legacy SAML

Note

SharePoint app-only access requires a certificate. A client secret is not accepted by the SharePoint /_api endpoints — use GraphClient for secret-based app-only. See why a client secret doesn't work for SharePoint.

from office365.sharepoint.client_context import ClientContext

ctx = ClientContext("https://contoso.sharepoint.com/sites/team").with_client_certificate(
    "contoso.onmicrosoft.com",
    client_id="00000000-0000-0000-0000-000000000000",
    thumbprint="AA11BB22CC33DD44EE55FF66AA77BB88CC99DD00",
    cert_path="./private.pem",
)

web = ctx.web.get().execute_query()
print(web.title)

Ready-made scripts: certificate · certificate (private key) · custom scopes · interactive · device flow · username/password · access token · cookies · NTLM

GraphClient (Microsoft Graph)

Flow Access Method
Client secret App-only with_client_secret(client_id, client_secret)
Certificate App-only with_certificate(client_id, thumbprint, private_key)
Interactive Delegated (MFA) with_token_interactive(client_id)
Device code Delegated (MFA) with_device_flow(client_id)
Username / password Delegated with_username_and_password(client_id, username, password)
Custom token Either GraphClient(token_callback, tenant=...)
from office365.graph_client import GraphClient

client = GraphClient(tenant="contoso.onmicrosoft.com").with_client_secret(
    client_id="00000000-0000-0000-0000-000000000000",
    client_secret="your-client-secret",
)

users = client.users.get().execute_query()
print(len(users), "users")

Ready-made scripts: client secret · certificate · interactive · device flow · username/password · custom token callback

Quick start

SharePoint list — read and write items

from office365.sharepoint.client_context import ClientContext

ctx = ClientContext("https://contoso.sharepoint.com/sites/team").with_client_certificate(
    "contoso.onmicrosoft.com", client_id, thumbprint, cert_path="./private.pem"
)

tasks = ctx.web.lists.get_by_title("Tasks")
item = tasks.add_item({"Title": "Write the release notes"}).execute_query()
print("Created item:", item.id)

for task in tasks.items.get_all().execute_query():
    print(task.properties["Title"])

Microsoft Graph — send an email

from office365.graph_client import GraphClient

client = GraphClient(tenant="contoso.onmicrosoft.com").with_client_secret(client_id, client_secret)

client.me.send_mail(
    subject="Hello from Graph API",
    body="This email was sent using the Microsoft Graph API.",
    to_recipients=["alex@contoso.onmicrosoft.com"],
).execute_query()

Examples by product

Every snippet below is lifted from a runnable script. Follow the More → link for the full example and its required permissions.

SharePoint (ClientContext)

# Lists and items
tasks = ctx.web.lists.get_by_title("Tasks")
item = tasks.add_item({"Title": "Ship it", "Status": "Active"}).execute_query()
tasks.items.get_all().execute_query()               # or .filter("Status eq 'Active'").get_all()

# Files and folders
folder = ctx.web.get_folder_by_server_relative_url("/sites/team/Shared Documents")
with open("report.pdf", "rb") as f:
    file = folder.files.upload(f).execute_query()

with open("report.pdf", "wb") as f:
    ctx.web.get_file_by_server_relative_path("/sites/team/Shared Documents/report.pdf").download(f).execute_query()

# Large file (chunked upload session)
with open("video.mp4", "rb") as f:
    folder.files.create_upload_session(f, 10 * 1024 * 1024).execute_query()
Area Examples
Lists and items lists · listitems · views
Files and folders files · folders
Sites and webs sites · webs · hubsites
Search search
Permissions and sharing permissions · sharing
Fields and content types fields · contenttypes
Pages and navigation pages · navigation
Taxonomy and profiles taxonomy · userprofile
Tenant administration tenant · groups · users
Webhooks, migration, advanced webhooks · migration · advanced

OneDrive

uploaded = client.me.drive.root.upload_file("report.xlsx").execute_query()

with open("report.xlsx", "wb") as f:
    client.me.drive.root.get_by_path("report.xlsx").download(f).execute_query()

More →

Teams

team = client.teams.create_and_wait("Contoso Project", "All project collaboration").execute_query()

channel = team.channels.add("Project Chat").execute_query()
channel.messages.add("Hello team!").execute_query()

More →

Outlook (mail and calendar)

from datetime import datetime, timedelta, timezone

# Send mail
client.me.send_mail(
    subject="Hello from Graph API",
    body="This email was sent using the Microsoft Graph API.",
    to_recipients=["alex@contoso.onmicrosoft.com"],
).execute_query()

# Create a calendar event
when = datetime.now(timezone.utc) + timedelta(days=1)
client.me.calendar.events.add(
    subject="Team Lunch",
    body="Let's grab lunch together.",
    start=when,
    end=when + timedelta(hours=1),
    attendees=["alex@contoso.onmicrosoft.com"],
).execute_query()

More →

Entra ID

from office365.directory.users.password_profile import PasswordProfile
from office365.directory.users.profile import UserProfile

profile = UserProfile(
    displayName="Alex Wilber",
    userPrincipalName="alex@contoso.onmicrosoft.com",
    mailNickname="alex",
    accountEnabled=True,
    passwordProfile=PasswordProfile(password="P@ssw0rd!", forceChangePasswordNextSignIn=True),
)
user = client.users.add(profile).execute_query()
print(user.display_name)

More →

Planner

group = client.groups.get_by_name("My Sample Team").get().execute_query()
plans = group.planner.plans.get().execute_query()
task = client.planner.tasks.add("Update client list", plans[0].id).execute_query()
print(task.title)

More →

To Do

for task_list in client.me.todo.lists.get().execute_query():
    print(task_list.display_name, len(task_list.tasks.get().execute_query()), "tasks")

More →

OneNote

with open("Sample.html", "rb") as page_html:
    page = client.me.onenote.pages.add(presentation_file=page_html).execute_query()
print(page.links.oneNoteWebUrl)

More →

More services — Admin, Reports, Defender, Intune, Purview, Bookings, Communications, Security, Insights, Backup Storage

Microsoft 365 admin

announcement = client.admin.service_announcement
health = announcement.health_overviews.get().execute_query()
issues = announcement.issues.get().execute_query()
messages = announcement.messages.get().execute_query()

More →

Usage reports

report = client.reports.get_email_activity_counts("D30").execute_query()
print(report.value)  # CSV payload

More →

Microsoft Defender

alerts = client.security.alerts_v2.top(20).get().execute_query()
for alert in alerts:
    print(alert)

More →

Microsoft Intune

devices = client.device_management.managed_devices.get().execute_query()
for device in devices:
    print(device.device_name, device.operating_system, device.compliance_state)

More →

Microsoft Purview

labels = client.security.data_security_and_governance.sensitivity_labels.get().execute_query()
for label in labels:
    print(label.display_name, label.id)

More →

Microsoft Bookings

businesses = client.solutions.booking_businesses.get().execute_query()
for business in businesses:
    print(business.display_name)

More →

Cloud communications

presence = client.users["alex@contoso.onmicrosoft.com"].presence.get().execute_query()
print(presence.availability, presence.activity)

More →

Security (threat intelligence)

host = client.security.threat_intelligence.hosts["contoso.com"].get().execute_query()
reputation = host.reputation.get().execute_query()
print(reputation.properties.get("score"))

More →

Insights

for item in client.me.insights.trending.get().execute_query():
    print(item.resource_reference)

More →

Backup Storage

backup = client.solutions.backup_restore
backup.get().execute_query()
print(backup.service_status.status)

More →

Common patterns

Deferred execution. Requests are queued and sent only when you call execute_query(). Chain the fluent methods first, then execute once.

items = (
    ctx.web.lists.get_by_title("Tasks")
    .items.select(["Title", "Status"])
    .filter("Status eq 'Active'")
    .order_by("Title")
    .top(100)
    .get()
    .execute_query()
)

Reading large collections. Use get_all() to follow paging automatically, and page_size to stay under server limits.

items = ctx.web.lists.get_by_title("Orders").items.get_all(page_size=2000).execute_query()

files = ctx.web.get_folder_by_server_relative_url("/sites/team/Shared Documents").get_files(
    recursive=True, page_size=2000
).execute_query()

Batching. Dispatch many queued operations in one round trip. Raise concurrency to run batches in parallel; throttled sub-requests are retried individually, honoring Retry-After.

for row in rows:
    ctx.web.lists.get_by_title("Contacts").add_item(row)

ctx.execute_batch()                     # sequential
ctx.execute_batch(concurrency=5)        # up to 5 batches in flight

# Graph
for user in new_users:
    client.users.add(user)
client.execute_batch(concurrency=5)

Large lists (> 5,000 items). SharePoint refuses filtered/sorted queries on non-indexed columns. The library can tell you what to index and pre-flight the query.

from office365.sharepoint.listitems.caml import Caml, CamlQuery

query = (
    CamlQuery.builder()
    .where(Caml.text("Status").eq("Active"))
    .order_by("ID")
    .row_limit(2000, paged=True)
    .build()
)

lst.check_query(query)                          # raises with a clear message if an index is missing
print(lst.index_candidates(query))              # columns worth indexing
lst.ensure_indexed("Status").execute_query()    # create the index (builds in the background)

Import and export DataFrames. Optional helpers turn list items into pandas / Polars frames and back.

lst = ctx.web.lists.ensure_list("Orders").execute_query()

df = lst.to_dataframe().execute_query().value
lst.from_dataframe(df, chunksize=100).execute_query()

Dependencies

Install the core package on its own, or add the extras you need:

Extra Adds Use it for
azure azure-storage-blob, cryptography Azure Blob Storage and certificate helpers
excel openpyxl Reading and writing Excel workbooks
examples faker Running the sample scripts
ntlm requests-ntlm On-premises SharePoint with NTLM
pandas pandas to_dataframe() / from_dataframe()
parquet pyarrow Parquet import and export
duckdb duckdb Querying exported data locally
sql sqlalchemy SQL-backed import and export
notebooks jupyter, nbformat, jinja2 Running the notebook samples
pip install "office365-rest-python-client[pandas,excel]"

Contributing

Issues and pull requests are welcome — please use the issue tracker for bugs and ideas.

git clone https://github.com/vgrem/office365-rest-python-client.git
cd office365-rest-python-client
uv sync --all-extras
pytest --offline -q

See CONTRIBUTING.md for the full development setup. Run the gate before submitting: ruff check ., ruff format --check ., pyright and pytest --offline -q.

Support

If this project saves you time, please consider sponsoring its development. ⭐

License

Released under the MIT License.

Releases

Sponsor this project

Packages

Used by

Contributors

Languages