SharePoint and OneDrive file APIs

SharePoint document libraries and OneDrive - uploads of any size, downloads, folders and Entra lookups.

This page covers working with files in Microsoft 365 from Python services - SharePoint document libraries, OneDrive, uploads of any size and downloads - plus looking users up in the Entra directory. For mailboxes, calendars and the overall connection setup, start with the Microsoft 365 tutorial.

All the examples obtain their client the same way:

conn = self.microsoft.cloud['My MS365 Connection']

SharePoint document libraries are drives

A SharePoint site stores its files in document libraries, and each document library is a drive with the same API that OneDrive uses. This is the key fact that connects the two - once you have a drive, everything below works identically for SharePoint and OneDrive.

# -*- coding: utf-8 -*-

from zato.server.service import Service

class ListLibraryFiles(Service):
    name = 'api.sharepoint.list-library-files'

    def handle(self):

        conn = self.microsoft.cloud['My MS365 Connection']

        # Get a site by its hostname and path
        sharepoint = conn.sharepoint()
        site = sharepoint.get_site('mycompany.sharepoint.com', '/sites/procurement')

        # The site's default document library, as a drive
        library = site.get_default_document_library()

        # List what the library's root folder contains
        root = library.get_root_folder()

        for item in root.get_items():
            self.logger.info(f'{item.name} ({item.size} bytes)')

A site can have more than one document library:

# All document libraries of a site
for library in site.list_document_libraries():
    self.logger.info(f'Library: {library.name}')

# A specific one, by its drive ID
library = site.get_document_library(drive_id)

Uploading files

upload_file on any folder handles files of any size. Files up to 4 MB go up in a single request, and anything larger should be uploaded in chunks:

# -*- coding: utf-8 -*-

from zato.server.service import Service

class UploadReport(Service):
    name = 'api.sharepoint.upload-report'

    def handle(self):

        conn = self.microsoft.cloud['My MS365 Connection']

        sharepoint = conn.sharepoint()
        site = sharepoint.get_site('mycompany.sharepoint.com', '/sites/procurement')

        library = site.get_default_document_library()
        folder = library.get_root_folder()

        # A small file - one request is enough
        uploaded = folder.upload_file('/data/reports/summary.pdf')

        self.logger.info(f'Uploaded {uploaded.name} -> {uploaded.web_url}')

Uploading large files in chunks

For large files, pass upload_in_chunks=True and the upload becomes resumable - the file is sent in chunks and an interrupted chunk can be retried without starting over:

# A large file, uploaded in 5 MB chunks
uploaded = folder.upload_file(
    '/data/exports/full-catalog.zip',
    upload_in_chunks=True,
    conflict_handling='replace',
)

Details worth knowing:

  • The default chunk size is 5 MB and a custom chunk_size must be a multiple of 327,680 bytes - a Microsoft Graph requirement
  • conflict_handling is one of fail, replace or rename and decides what happens when a file of that name already exists
  • Instead of a path, an open file-like object can be uploaded with stream= and stream_size=, with item_name= naming the file on the server
from io import BytesIO

data = BytesIO(report_bytes)
data_size = len(report_bytes)

uploaded = folder.upload_file(
    None,
    item_name='generated-report.pdf',
    stream=data,
    stream_size=data_size,
    upload_in_chunks=True,
)

Downloading files

# By path within the drive
item = library.get_item_by_path('/Contracts/2026/master-agreement.docx')

# Download to a local directory
item.download(to_path='/tmp/contracts')

# Or straight into memory
from io import BytesIO
buffer = BytesIO()
item.download(output=buffer)

Folders and file management

root = library.get_root_folder()

# Create a folder
invoices = root.create_child_folder('Invoices 2026')

# Search the whole drive
for item in library.search('master agreement'):
    self.logger.info(f'Found: {item.web_url}')

# Move, copy, delete
item.move(invoices)
item.copy(invoices, name='copy-of-agreement.docx')
item.delete()

# Share with a link
link = item.share_with_link(share_type='view')

Every item includes its metadata as attributes - item.name, item.size, item.web_url, item.created, item.modified, item.created_by, and item.is_folder or item.is_file tell the two kinds apart.

OneDrive

The same drive API, reached through conn.storage() instead of a SharePoint site:

# -*- coding: utf-8 -*-

from zato.server.service import Service

class UploadToOneDrive(Service):
    name = 'api.onedrive.upload'

    def handle(self):

        conn = self.microsoft.cloud['My MS365 Connection']

        # A specific user's drive - client-credentials connections
        # act on behalf of the application, so name the user explicitly
        storage = conn.storage(resource='alexis@mycompany.com')
        drive = storage.get_default_drive()

        folder = drive.get_root_folder()
        uploaded = folder.upload_file('/data/reports/summary.pdf')

        self.response.payload = {'web_url': uploaded.web_url}

Entra directory lookups

The same connection reads the Entra directory, which is how file activity gets connected to people:

# -*- coding: utf-8 -*-

from zato.server.service import Service

class GetUserDetails(Service):
    name = 'api.entra.get-user-details'

    def handle(self):

        conn = self.microsoft.cloud['My MS365 Connection']
        directory = conn.directory()

        # By user principal name or by ID
        user = directory.get_user('alexis@mycompany.com')

        self.logger.info(f'{user.full_name} - {user.job_title}, {user.department}')

        # The user's manager and direct reports
        manager = directory.get_user_manager('alexis@mycompany.com')

        for report in directory.get_user_direct_reports('alexis@mycompany.com'):
            self.logger.info(f'Direct report: {report.display_name}')

One caveat - directory.get_current_user() works only with delegated authentication. Connections defined in the Dashboard use client credentials, where there is no current user, so always look users up by name or ID as above.

Auditing

Every Microsoft Graph call made through the connection is recorded in the audit log with its duration and outcome, with no code needed on your part.