Table of contents

Add hundreds of integrations to your product through Merge’s Unified API
Get a demo

How to integrate with the Dropbox API via Python

Michael Nyamande
@Merge

Dropbox, a leading cloud storage and file sharing platform, helps teams streamline collaboration, organize content, safeguard backups, and increasingly leverage AI to automate workflows and insights.

Based on all of the file and folder data collected and used in Dropbox, integrating it with your internal applications or your product can offer a wealth of benefits.

To that end, we’ll walk through how you can connect to Dropbox’s API endpoints with Python. 

But first, let’s review how Dropbox’s API works.

Dropbox API Overview

Before writing any code, you need to understand how the Dropbox API handles authentication and structures its endpoints as this directly impacts how you build your integration.

Dropbox uses OAuth 2.0 for authentication, but unlike many modern APIs, it doesn't provide refresh token rotation or simplified authentication flows. You need to manually implement the complete OAuth 2.0 flow, including generating authorization URLs, handling user consent, and managing token storage. This manual approach gives you control but requires significantly more implementation work compared to APIs that offer SDK-managed authentication.

Once authenticated, the API is primarily split across two domains:

  • <code class="blog_inline-code">api.dropboxapi.com</code>: This is the control plane for your files. All metadata operations, like listing files and folders, checking permissions, and retrieving file information, happen here
  • <code class="blog_inline-code">content.dropboxapi.com</code>: This domain is purely for the data itself. The actual file content is uploaded to and downloaded from this endpoint

This separation is designed for performance and scalability, but it requires you to coordinate requests between two different services.

You’ll also likely interact with the following endpoints:

  • <code class="blog_inline-code">files_list_folder</code> retrieves the contents of a directory
  • <code class="blog_inline-code">files_upload</code> handles uploads for files smaller than 150 MB
  • <code class="blog_inline-code">files_download</code> downloads a file from a user's Dropbox
  • <code class="blog_inline-code">files_upload_session_start, files_upload_session_append_v2</code>, and <code class="blog_inline-code">files_upload_session_finish</code> are a trio of endpoints that work together to handle large file uploads through a chunked process

Related: How to get your Dropbox API key

Prerequisites

To follow along with this tutorial, you need the following:

  • Dropbox account: Make sure you have access to Dropbox and can create applications in the App Console. You need developer permissions to register apps and generate API credentials
  • Python 3.x. Verify that Python 3 and pip are installed and configured on your system

You also need to install a few dependencies:

pip install dropbox python-dotenv

This installs the official Dropbox SDK and python-dotenv. The Dropbox SDK provides Python classes and methods for interacting with the Dropbox API, and python-dotenv allows you to load environment variables from an ENV file, keeping your API credentials secure and separate from your code.

Always consider installing packages within a Python virtual environment (<code class="blog_inline-code">venv</code>, <code class="blog_inline-code">virtualenv</code>, or <code class="blog_inline-code">conda</code>). This isolates your project's dependencies and ensures consistent versions across development, testing, and production environments.

Dropbox application setup

To access the Dropbox API, you need to create and register your application in the Dropbox App Console.

Once you've signed into your Dropbox account, click Create app and select Scoped access. Then, select the type of access you need. For most use cases, Full Dropbox is fine as it allows your app to access all files and folders in a user's instance of Dropbox. App folder restricts access to a single, dedicated folder for your app. Finally, name your app and click Create app.

Creating a Dropbox application

Navigate to the Permissions tab. To perform basic file operations, you need to enable the following scopes:

  • <code class="blog_inline-code">files.metadata.read</code> to list and view file/folder metadata
  • <code class="blog_inline-code">files.content.read</code> to download file content
  • <code class="blog_inline-code">files.content.write</code> to upload or modify files
Permissions tab

Then go to the Settings tab and find your App key and App secret. These are your application's credentials; treat them like passwords and store them securely.

Create a file in your project directory and name it <code class="blog_inline-code">.env</code>. Save your credentials here to store them as environment variables:

APP_KEY = "YOUR KEY HERE"
APP_SECRET = "YOUR SECRET HERE"

Authentication with OAuth 2.0

Dropbox uses OAuth 2.0 for authorization, but unlike some APIs, it doesn't provide helper libraries for the complete flow. You’ll need to implement the authorization process manually, which gives you control but requires more code.

Here's a complete OAuth 2.0 implementation:

import dropbox
import json
import os
from dropbox.oauth import DropboxOAuth2FlowNoRedirect
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()

TOKEN_FILE = "dropbox_token.json"

def save_tokens(access_token, refresh_token):
    """Save the access token and refresh token to a file."""
    token_data = {
        "access_token": access_token,
        "refresh_token": refresh_token
    }
    with open(TOKEN_FILE, 'w') as token_file:
        json.dump(token_data, token_file, indent=2)

def load_tokens():
    """Load the access token and refresh token from a file if it exists."""
    if not os.path.exists(TOKEN_FILE):
        return None, None

    try:
        with open(TOKEN_FILE, 'r') as token_file:
            data = json.load(token_file)
            return data.get("access_token"), data.get("refresh_token")
    except (json.JSONDecodeError, KeyError):
        return None, None

def create_dropbox_client(access_token, refresh_token,app_key,app_secret):
    """Create and validate a Dropbox client with the given token."""
    client = dropbox.Dropbox(
        oauth2_access_token=access_token,
        oauth2_refresh_token=refresh_token,
        app_key=app_key,
        app_secret=app_secret
    )
    account_info = client.users_get_current_account()
    return client, account_info

def perform_oauth_flow(app_key, app_secret):
    """Perform the OAuth2 flow to get new access and refresh tokens."""
    auth_flow = DropboxOAuth2FlowNoRedirect(app_key, app_secret, token_access_type='offline')
    authorize_url = auth_flow.start()

    print(f"1. Go to this URL: {authorize_url}")
    print("2. Click 'Allow' (you may need to log in first).")
    print("3. Copy the authorization code.")

    auth_code = input("Enter the authorization code here: ").strip()
    oauth_result = auth_flow.finish(auth_code)
    return oauth_result.access_token, oauth_result.refresh_token

def authenticate_dropbox():
    """
    Authenticates with Dropbox using saved token or OAuth2 flow.
    Reads app credentials from environment variables.
    Returns a Dropbox client instance.
    """
    # Get credentials from environment variables
    app_key = os.getenv('APP_KEY')
    app_secret = os.getenv('APP_SECRET')

    if not app_key or not app_secret:
        print("Error: APP_KEY and APP_SECRET environment variables must be set.")
        return None

    # Try existing tokens first
    access_token, refresh_token = load_tokens()

    if access_token:
        try:
            client, account_info = create_dropbox_client(access_token, refresh_token,app_key,app_secret)
            print(f"Using saved token. Authenticated as: {account_info.name.display_name}")
            return client
        except Exception as error:
            print(f"Error using saved token: {error}")
            # Proceed to OAuth flow for new tokens

    # Get new tokens via OAuth flow
    try:
        access_token, refresh_token = perform_oauth_flow(app_key, app_secret)
        save_tokens(access_token, refresh_token)

        client, account_info = create_dropbox_client(access_token, refresh_token,app_key,app_secret)
        print(f"Successfully authenticated as: {account_info.name.display_name}")
        return client

    except Exception as error:
        print(f"Error during authentication: {error}")
        return None

if __name__ == '__main__':
    dropbox_client = authenticate_dropbox()

    if dropbox_client:
        print("Authentication successful! You can now use the dropbox_client object.")

This code uses the Dropbox Python SDK and the DropboxOAuth2FlowNoRedirect class to generate an authorization link. You visit this link in your browser, approve access, and then paste the code you get back into the app. That's how the app gets permission to use your Dropbox account.

The code also includes functions for storing and retrieving tokens, as well as creating the Dropbox client with the access and refresh tokens. While the SDK automatically refreshes expired access tokens, it doesn't manage token storage or allow rotating refresh tokens. You're responsible for securely storing these tokens and handling refresh token revocation according to your security policy.

To run the script, create a file named <code class="blog_inline-code">dropbox_auth.py</code> and add the previous code. Run it using the following:

python dropbox_auth.py

Your output should look like this:

1. Go to: https://www.dropbox.com/oauth2/authorize?client_id=your_app_key...
2. Click 'Allow' (you might have to log in first)
3. Copy the authorization code.
Enter the authorization code here: [you enter the code]

Core file operations

With authentication established, you can start working with files. The following are some of the key file operations you can perform with the Dropbox API.

List folder contents

To list the contents of a Dropbox folder, you need to handle pagination because Dropbox returns results in batches. The following example demonstrates how to retrieve all files and folders within a directory, iterating through each page of results until the entire folder is listed:

 import dropbox
from dropbox_auth import authenticate_dropbox

def list_folder_contents(dbx, folder_path=""):
    """
    Lists the contents of a Dropbox folder, handling pagination.
    An empty folder_path represents the root directory.
    """
    try:
        result = dbx.files_list_folder(folder_path)
        entries = result.entries

        # Paginate if there are more entries
        while result.has_more:
            result = dbx.files_list_folder_continue(result.cursor)
            entries.extend(result.entries)

        for entry in entries:
            if isinstance(entry, dropbox.files.FileMetadata):
                print(f"- File: {entry.name} (ID: {entry.id})")
            elif isinstance(entry, dropbox.files.FolderMetadata):
                print(f"- Folder: {entry.name} (ID: {entry.id})")

    except dropbox.exceptions.ApiError as e:
        print(f"API error listing folder '{folder_path}': {e}")

if __name__ == '__main__':
    dbx = authenticate_dropbox()

    if dbx:
        print("\nListing root folder contents:")
        list_folder_contents(dbx, "")  # List root
 

The code uses the files_list_folder method to retrieve the list of files within the folder. It then iterates over that list, printing the name and ID of each of the entries.

Note: When passing the <code class="blog_inline-code">folder_path</code> to the <code class="blog_inline-code">files_list_folder</code>, be mindful that Dropbox enforces strict path formatting. Absolute paths require a leading slash "/", and an empty "" targets the root. If the folder is shared, your app must have the necessary scopes enabled and may need to use its <code class="blog_inline-code">namespace_id</code> or enable <code class="blog_inline-code">include_mounted_folders</code> to ensure it's included in the results. This is because shared content can exist outside the user's primary namespace.

Related: How to retrieve folders via the Dropbox API

Upload files

The Dropbox API provides different mechanisms for uploading files based on their size. For files smaller than 150 MB, the <code class="blog_inline-code">files_upload()</code> method is a straightforward approach. You can perform the upload in a single request by reading the file's binary content and passing it to the function along with the desired Dropbox path. An important parameter is mode, which controls how Dropbox handles the upload if a file with the same name already exists. Supported modes include <code class="blog_inline-code">WriteMode('overwrite')</code> to replace the file, <code class="blog_inline-code">WriteMode('add')</code> to keep the existing file and create a renamed copy, and <code class="blog_inline-code">WriteMode('update')</code> to overwrite only if the current revision matches a specific revision ID.

The following is a code snippet showing how to upload small files:

 import dropbox
from dropbox_auth import authenticate_dropbox

def upload_small_file(dbx, local_path, dropbox_path):
    """Uploads a small file (< 150 MB) to Dropbox."""
    try:
        with open(local_path, 'rb') as f:
            dbx.files_upload(
                f.read(), 
                dropbox_path, 
                mode=dropbox.files.WriteMode('overwrite')
            )
        print(f"Successfully uploaded '{local_path}' to '{dropbox_path}'")
    except FileNotFoundError:
        print(f"Error: Local file '{local_path}' not found.")
    except dropbox.exceptions.ApiError as e:
        print(f"API error uploading file: {e}")

if __name__ == '__main__':
    dbx = authenticate_dropbox()

    if dbx:
        # Example usage (replace with actual file paths)
        upload_small_file(dbx, "report.pdf", "/reports/2024_report.pdf")

For larger files, the API requires a more robust, session-based approach to ensure reliability. This method involves sending the file in smaller, sequential chunks. The process begins by calling <code class="blog_inline-code">files_upload_session_start()</code> with the first chunk of data, which returns a unique session_id. For all subsequent chunks, use <code class="blog_inline-code">files_upload_session_append_v2()</code>, passing the session_id and using an <code class="blog_inline-code">UploadSessionCursor</code> to track the offset.

Once the final chunk is sent, a call to <code class="blog_inline-code">files_upload_session_finish()</code> commits the file to its path in Dropbox, completing the upload. This chunked approach ensures that large uploads can withstand connection interruptions as only the failed chunk needs to be resent, not the entire file.

Here's a code snippet that uploads a large file:

import dropbox
import os
from dropbox_auth import authenticate_dropbox

def upload_large_file(dbx, local_path, dropbox_path, chunk_size=4 * 1024 * 1024):
    """Uploads a large file (> 150 MB) to Dropbox using an upload session."""
    try:
        file_size = os.path.getsize(local_path)
        with open(local_path, 'rb') as f:
            # Start session
            upload_session_start_result = dbx.files_upload_session_start(f.read(chunk_size))
            cursor = dropbox.files.UploadSessionCursor(session_id=upload_session_start_result.session_id, offset=f.tell())
            commit = dropbox.files.CommitInfo(path=dropbox_path, mode=dropbox.files.WriteMode('overwrite'))

            print(f"Uploading '{local_path}'... {f.tell() / file_size:.0%}")

            # Append remaining chunks
            while f.tell() < file_size:
                if (file_size - f.tell()) <= chunk_size:
                    dbx.files_upload_session_finish(f.read(chunk_size), cursor, commit)
                    break
                else:
                    dbx.files_upload_session_append_v2(f.read(chunk_size), cursor)
                    cursor.offset = f.tell()
                print(f"Uploading '{local_path}'... {f.tell() / file_size:.0%}")

            print(f"Successfully uploaded '{local_path}' to '{dropbox_path}'")

    except FileNotFoundError:
        print(f"Error: Local file '{local_path}' not found.")
    except dropbox.exceptions.ApiError as e:
        print(f"API error uploading large file: {e}")

if __name__ == '__main__':
    dbx = authenticate_dropbox()

    if dbx:
        # Example usage (replace with actual file path)
        # Create a dummy large file for testing: fsutil file createnew large_file.bin 160000000
        upload_large_file(dbx, "large_file.bin", "/large_files/large_file.bin")

Download files

Just as with uploads, downloading files from Dropbox is a core operation. The <code class="blog_inline-code">files_download()</code> method allows you to retrieve a file's content and its metadata in a single call. However, keep in mind that the API does not provide a direct way to inspect the content type or generate previews; your application must handle the file content based on its extension or other metadata.

Here's a script to download a file:

import dropbox
from dropbox_auth import authenticate_dropbox

def download_file(dbx, dropbox_path, local_path):
    """Downloads a file from Dropbox."""
    try:
        metadata, res = dbx.files_download(path=dropbox_path)
        with open(local_path, 'wb') as f:
            f.write(res.content)
        print(f"Successfully downloaded '{dropbox_path}' to '{local_path}'")
        print(f"Metadata: Name: {metadata.name}, Size: {metadata.size} bytes")
    except dropbox.exceptions.HttpError as e:
        print(f"HTTP error downloading file: {e}")
    except dropbox.exceptions.ApiError as e:
        print(f"API error downloading file: {e.path_lookup}")

if __name__ == '__main__':
    dbx = authenticate_dropbox()

    if dbx:
        # Example usage (replace with actual file paths)
        download_file(dbx, "/reports/2024_report.pdf", "downloaded_report.pdf")

In this code, the <code class="blog_inline-code">download_file</code> function takes a Dropbox client, the path to the file in Dropbox, and the local path where you want to save the file. It fetches the file from Dropbox and writes it to your local filesystem, printing out useful metadata like the file name and size. Error handling is included to catch and display any issues that occur during the download process, such as HTTP or API errors.

To run this code, copy it into a new file and save it as <code class="blog_inline-code">download_file.py</code>. Update the example file paths in the script to match the Dropbox file you want to download and your desired local filename. Then, open your terminal and run <code class="blog_inline-code">python download_file.py</code>.

All the code samples used in this tutorial are available on GitHub.

Related: A guide to getting files from dropbox

The broader challenge: integrating multiple file storage APIs

Building a feature-complete Dropbox integration requires you to solve several engineering problems. The OAuth 2.0 flow is manual, the API is split across domains, and large file uploads require a complex, stateful process.

Now, imagine your product needs to support a Google Drive integration. Google Drive uses a completely different resumable upload protocol, and Box has yet another strategy for chunking uploads. The SharePoint API architecture, based on Microsoft Graph, also has its own complexities.

In short, every new file storage platform forces you to learn and implement a new, provider-specific authentication system. You need to write custom logic for different data structures and file hierarchies. Upload and download mechanisms also vary, with different protocols, chunk sizes, and rate limits.

On top of that, each integration must be maintained separately as APIs change and new updates are released. This means supporting different platforms quickly spirals into a significant engineering and maintenance burden, diverting resources from your core product.

Connect your product to any file storage system via Merge

Instead of building and maintaining several file storage integrations individually, you can use Merge’s File Storage API to connect to all of them.

 

Merge's supported file storage integrations
A snapshot of the file storage integrations Merge supports

Merge also offers:

  • Unified authentication: You implement one authentication flow, and Merge handles the provider-specific OAuth 2.0 handshakes, token refreshes, and credential management for you

  • Normalized data: Files, folders, and permissions are mapped to a standardized Common Model. You write your logic once against Merge's schema, and it works seamlessly across all its providers

  • Simplified operations: Complex processes, like large file uploads, are handled with a single, consistent API call. Merge manages the underlying chunking, session management, and error handling

  • Consistent pagination and real-time updates: Merge lets you page through files and folders the same way across all providers, so you don't have to write custom logic for each platform. Additionally, webhook-based sync lets you receive notifications about file changes, additions, or deletions, keeping your app up to date automatically without polling or manual checks

Learn how Merge can help you build file storage integrations faster and maintain them with ease by scheduling a demo with one of our integration experts.

“It was the same process, go talk to their team, figure out their API. It was taking a lot of time. And then before we knew it, there was a laundry list of HR integrations being requested for our prospects and customers.”

Name
Position
Position
Michael Nyamande
@Merge

Read more

How to decide between syncing and MCP for enterprise search

Engineering

5 ways to use the Model Context Protocol

AI

Semantic search: how to implement it for enterprise search

AI

Subscribe to the Merge Blog

Get stories from Merge straight to your inbox

Subscribe

But Merge isn’t just a Unified 
API product. Merge is an integration platform to also manage customer integrations.  gradient text
But Merge isn’t just a Unified 
API product. Merge is an integration platform to also manage customer integrations.  gradient text
But Merge isn’t just a Unified 
API product. Merge is an integration platform to also manage customer integrations.  gradient text
But Merge isn’t just a Unified 
API product. Merge is an integration platform to also manage customer integrations.  gradient text