How to: Upload
This guide details the full flow to upload files using the Frame.io V4 API.
Prerequisites
- You have a Frame.io V4 account administered via the Adobe Admin Console, OR you have switched to Adobe authentication for your account user
- You have logged into the Adobe Developer Console and have added the Frame.io API to a new or existing project
- You have generated the appropriate Authentication credentials for your project
- You have succfully 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)
. The local endpoint would be used when the media is locally accessible to your application, similar to dragging a file from your desktop; the remote upload option would be used 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.
Copied to your clipboard{"data": {"name": "my_file.jpg","source_url": "https://upload.wikimedia.org/wikipedia/commons/e/e1/White_Pixel_1x1.jpg"}}
A successful request will yield a response like the one below.
Copied to your clipboard{"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.
Copied to your clipboard{"data": {"name": "my_file.jpg","file_size": 50645990}}
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. See Multi-part Upload for next steps.
Copied to your clipboard{"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"}}
NOTE: 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 themedia_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 formedia_type
isimage/jpeg
. Therefore, the value forContent-Type
must also beimage/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.
Create File (local upload) with Multi-part Upload
Copied to your clipboardimport requestsimport mathfrom typing import Listfrom tqdm import tqdm # For progress bardef 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 extensionif content_type is None:detected_content_type, _ = mimetypes.guess_type(file_path)content_type = detected_content_type # Default fallbackprint(f"Detected content type: {content_type}")# Get file sizewith open(file_path, 'rb') as f:f.seek(0, 2) # Seek to end of filefile_size = f.tell()# Calculate chunk size if not providedif 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 chunkwith 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_sizeend_byte = min(start_byte + chunk_size, file_size)# Read chunk from filef.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 signatureresponse = 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 Falseelse:print(f"Chunk {i+1} uploaded successfully!")pbar.update(1)return Trueexcept Exception as e:print(f"Error during upload: {str(e)}")return False# Example usageif __name__ == "__main__":# Replace these with your actual valuesfile_path = "/Users/MyComputer/local_upload/sample.jpg" # Path to your fileupload_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!")