> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.staging.frame.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.staging.frame.io/_mcp/server.

# How to: Upload

This guide details the full flow to upload files using the Frame.io V4 API.

## Prerequisites

Before you begin uploading files, ensure you have completed these setup steps:

#### Frame.io V4 Account

You have a Frame.io V4 account administered via the [Adobe Admin Console](https://adminconsole.adobe.com/), OR you have [switched to Adobe authentication](https://help.frame.io/en/articles/11758018-connecting-to-adobe-authentication) for your account user

#### Adobe Developer Console Setup

You have logged into the [Adobe Developer Console](https://developer.adobe.com/console) and have added the Frame.io API to a new or existing project

#### Authentication Credentials

You have generated the [appropriate Authentication credentials](https://developer.adobe.com/frameio/guides/Authentication/) for your project

#### Access Token

You have successfully used those credentials to generate an access token

## Choosing your upload method

There are two ways to upload a file using the Frame.io API: `Create File (local upload)` and `Create File (remote upload)`.

#### Local Upload

Use when the media is locally accessible to your application, similar to dragging a file from your desktop

#### Remote Upload

Use when the media is accessed over the network, such as through an integration with another service

In this guide we'll start with the simpler case of completing a remote upload.

## Remote Upload

To create a file through remote upload, select the **Create File (remote upload)** endpoint. The request body requires the file name and its source url.

### Request Example

```json
{ 
    "data": {
        "name": "my_file.jpg",
        "source_url": "https://upload.wikimedia.org/wikipedia/commons/e/e1/White_Pixel_1x1.jpg"
    }
}
```

### Response Example

A successful request will yield a response like the one below:

```json
{
    "data": {
        "id": "93e4079d-0a8a-4bf3-96cd-e6a03c465e5e",
        "name": "my_file.jpg",
        "status": "created",
        "type": "file",
        "file_size": 518,
        "updated_at": "2025-06-26T20:14:33.796116Z",
        "media_type": "image/jpeg",
        "parent_id": "2e426fe0-f965-4594-8b2b-b4dff1dc00ec",
        "project_id": "7e46e495-4444-4555-8649-bee4d391a997",
        "created_at": "2025-06-26T20:14:33.159489Z",
        "view_url": "https://next.frame.io/project/7e46e495-4444-4555-8649-bee4d391a997/view/93e4079d-0a8a-4bf3-96cd-e6a03c465e5e"
    },
    "links": {
        "status": "/v4/accounts/6f70f1bd-7e89-4a7e-b4d3-7e576585a181/files/93e4079d-0a8a-4bf3-96cd-e6a03c465e5e/status"
    }
}
```

## Local Upload

To create a file through local upload, select the **Create File (local upload)** endpoint. The request body requires the file name and its file size specified in bytes.

### Request Example

```json
{ 
    "data": {
        "name": "my_file.jpg",
        "file_size": 50645990
    }
}
```

### Response Example

If the request is successful, a placeholder file resource is created without any content. Depending on the file size, the response body will include one or more `upload_urls`. Given this example, we will need to manage this upload in multiple parts.

```json
{
    "data": {
        "id": "fa18ba7b-b3ee-4dd6-9b31-bd07e554241d",
        "name": "my_file.jpg",
        "status": "created",
        "type": "file",
        "file_size": 50645990,
        "updated_at": "2025-06-26T20:08:06.823170Z",
        "media_type": "image/jpeg",
        "parent_id": "2e426fe0-f965-4594-8b2b-b4dff1dc00ec",
        "project_id": "7e46e495-4444-4555-8649-bee4d391a997",
        "created_at": "2025-06-26T20:08:06.751313Z",
        "upload_urls": [
            {
                "size": 16881997,
                "url": "https://frameio-uploads-development.s3-accelerate.amazonaws.com/parts/fa18ba7b-b3ee-4dd6-9b31-bd07e554241d/part_1?..."
            },
            {
                "size": 16881997,
                "url": "https://frameio-uploads-development.s3-accelerate.amazonaws.com/parts/fa18ba7b-b3ee-4dd6-9b31-bd07e554241d/part_2?..."
            },
            {
                "size": 16881996,
                "url": "https://frameio-uploads-development.s3-accelerate.amazonaws.com/parts/fa18ba7b-b3ee-4dd6-9b31-bd07e554241d/part_3?..."
            }
        ],
        "view_url": "https://next.frame.io/project/7e46e495-4444-4555-8649-bee4d391a997/view/fa18ba7b-b3ee-4dd6-9b31-bd07e554241d"
    }
}
```

> **Warning**
>
> **Important Upload Requirements:**
>
> These are important details to keep in mind when sending the subsequent upload request(s):
>
> * The HTTP request method must be `PUT`
> * The `x-amz-acl` header must be included and be set to private
> * The `Content-Type` header must match the `media_type` specified in the original **Create File (local upload)** request. This is true even when uploading the file as separate parts. In the example above, the value for `media_type` is `image/jpeg`. Therefore, the value for `Content-Type` must also be `image/jpeg`.

## Multi-part Upload

When a given file results in more than one upload url, it may be useful to compose a shell script that splits up the source file into chunks and issues the same number of subsequent requests.

In the sample Python script below, we're passing in multiple upload urls in the `upload_urls` parameter.

### Python Implementation Example

**`Multi-part Upload Script`**

```python title="Multi-part Upload Script"
import requests
import math
from typing import List
from tqdm import tqdm  # For progress bar

def upload_file_in_chunks(file_path: str, upload_urls: list[str], content_type: str | None = None, chunk_size: int | None = None) -> bool:
    """
    Upload a file in chunks using presigned URLs.
    """
    try:
        # Auto-detect content type based on file extension
        if content_type is None:
            detected_content_type, _ = mimetypes.guess_type(file_path)
            content_type = detected_content_type # Default fallback
        
        print(f"Detected content type: {content_type}")
        
        # Get file size
        with open(file_path, 'rb') as f:
            f.seek(0, 2)  # Seek to end of file
            file_size = f.tell()
        
        # Calculate chunk size if not provided
        if chunk_size is None:
            chunk_size = math.ceil(file_size / len(upload_urls))
        
        print(f"File size: {file_size} bytes")
        print(f"Chunk size: {chunk_size} bytes")
        print(f"Number of chunks: {len(upload_urls)}")
        
        # Upload each chunk
        with open(file_path, 'rb') as f:
            with tqdm(total=len(upload_urls), desc="Uploading chunks") as pbar:
                for i, url in enumerate(upload_urls):
                    start_byte = i * chunk_size
                    end_byte = min(start_byte + chunk_size, file_size)
                    
                    # Read chunk from file
                    f.seek(start_byte)
                    chunk = f.read(end_byte - start_byte)
                    
                    print(f"Uploading chunk {i+1}: {len(chunk)} bytes")
                    
                    # Upload chunk with minimal headers matching the signature
                    response = requests.put(
                        url,
                        data=chunk,
                        headers={
                            'content-type': content_type,  
                            'x-amz-acl': 'private'       
                        }
                    )
                    
                    if response.status_code != 200:
                        print(f"Failed to upload chunk {i+1}. Status code: {response.status_code}")
                        print(f"Response text: {response.text}")
                        print(f"Response headers: {dict(response.headers)}")
                        return False
                    else:
                        print(f"Chunk {i+1} uploaded successfully!")
                    
                    pbar.update(1)
        
        return True
    
    except Exception as e:
        print(f"Error during upload: {str(e)}")
        return False

# Example usage
if __name__ == "__main__":
    # Replace these with your actual values
    file_path = "/Users/MyComputer/local_upload/sample.jpg"  # Path to your file
    upload_urls = [
        "https://frameio-uploads-development.s3-accelerate.amazonaws.com/parts/fa18ba7b-b3ee-4dd6-9b31-bd07e554241d/part_1?...", 
        "https://frameio-uploads-development.s3-accelerate.amazonaws.com/parts/fa18ba7b-b3ee-4dd6-9b31-bd07e554241d/part_2?...", 
        "https://frameio-uploads-development.s3-accelerate.amazonaws.com/parts/fa18ba7b-b3ee-4dd6-9b31-bd07e554241d/part_3?..."
    ]
    content_type = "image/jpeg"
    
    print("Starting file upload...")
    success = upload_file_in_chunks(file_path, upload_urls, content_type)
    
    if success:
        print("File upload completed successfully!")
    else:
        print("File upload failed!")
```

## Upload Flow Summary

#### Choose Upload Method

Decide between remote upload (file accessible via URL) or local upload (file on your system)

#### Create File Request

Make the initial request to create the file resource with required metadata

#### Handle Upload URLs

For local uploads, process the returned upload\_urls (single or multiple parts)

#### Upload File Content

Use PUT requests with proper headers to upload file content to the provided URLs

#### Verify Upload

Check the file status to confirm successful upload and processing

> **Info**
>
> **Next Steps**: Once your file is uploaded, you can use the returned file ID to add comments, create shares, or perform other operations using the Frame.io V4 API.