# 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](https://zato.io/tutorials/microsoft/365.html).

All the examples obtain their client with this line:

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

## SharePoint document libraries are drives {#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.

```python
# -*- 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:

```python
# 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 {#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:

```python
# -*- 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 {#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:

```python
# 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 specifies 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

```python
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 {#downloading-files}

```python
# 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 into an in-memory buffer
from io import BytesIO
buffer = BytesIO()
item.download(output=buffer)
```

## Folders and file management {#folders-and-file-management}

```python
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 {#onedrive}

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

```python
# -*- 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 {#entra-directory-lookups}

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

```python
# -*- 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 {#auditing}

Every Microsoft Graph call made through the connection is recorded in the [audit log](https://zato.io/docs/admin/audit-log/) with its duration and outcome, with no code needed on your part.
