Uploading Assets
To fully leverage the Scenario API for content generation, you often need to provide input assets, such as reference images for img2img, audio files for video lip-sync, or 3D models for 3D generation. The Scenario API provides two endpoints for uploading files:
POST /v1/assets- For uploading small images (< 6MB) using simple base64 uploadPOST /v1/uploads- For uploading larger images (≥ 6MB), audio, video, and 3D models using multipart upload
This guide will help you understand when to use each endpoint and how to upload files successfully.
Quick Reference: Which Endpoint to Use?
Section titled “Quick Reference: Which Endpoint to Use?”| File Type | File Size | Endpoint | Method |
|---|---|---|---|
| Images (JPEG, PNG, GIF, SVG, WebP, etc.) | < 6MB | /v1/assets | Simple base64 upload |
| Images (JPEG, PNG, GIF, SVG, WebP, etc.) | ≥ 6MB | /v1/uploads | Multipart upload |
| Audio (MP3, WAV, OGG, M4A) | Any size | /v1/uploads | Multipart upload |
| Video (MP4, WebM) | Any size | /v1/uploads | Multipart upload |
| 3D Models (GLB, GLTF, OBJ, FBX, etc.) | Any size | /v1/uploads | Multipart upload |
Uploading Images: /v1/assets
Section titled “Uploading Images: /v1/assets”The /v1/assets endpoint is the simplest way to upload small images (under 6MB). It accepts images in base64 format and returns an assetId immediately. For larger images (6MB or more), use the /v1/uploads endpoint with multipart upload instead.
Supported Image Formats
Section titled “Supported Image Formats”- JPEG -
image/jpeg - PNG -
image/png - GIF -
image/gif - SVG -
image/svg+xml - WebP -
image/webp - AVIF -
image/avif - TIFF -
image/tiff - HEIF / HEIC -
image/heic
Endpoint Details
Section titled “Endpoint Details”Endpoint: POST https://api.cloud.scenario.com/v1/assets?projectId={projectId}
Request Body
Section titled “Request Body”| Parameter | Type | Description |
|---|---|---|
image | string | Required. The image in base64 format |
name | string | Required. The name of the image file |
Optional Parameters:
hide- Boolean to hide the asset from the UIparentId- Parent asset ID for organizing assetscollectionIds- Array of collection IDs to add the asset to
Code Examples
Section titled “Code Examples”Python
Section titled “Python”import base64from scenario_sdk import Scenario
client = Scenario( api_key="YOUR_API_KEY", api_secret="YOUR_API_SECRET",)
# Read and encode image as base64file_path = "./my_image.png"with open(file_path, "rb") as image_file: encoded_string = base64.b64encode(image_file.read()).decode("utf-8")
# Upload imageresponse = client.assets.upload( name="my_image_base64_upload", image=encoded_string,)
asset_id = response.asset.idprint(f"Asset uploaded successfully! Asset ID: {asset_id}")TypeScript
Section titled “TypeScript”import Scenario from '@scenario-labs/sdk';import fs from 'fs';import path from 'path';
const client = new Scenario({ apiKey: 'YOUR_API_KEY', apiSecret: 'YOUR_API_SECRET',});
async function uploadImage() { const filePath = './my_image.png';
// Read and encode image to Base64 const fileBuffer = fs.readFileSync(filePath); const base64Image = fileBuffer.toString('base64');
// Upload image const response = await client.assets.upload({ name: path.basename(filePath), image: base64Image, });
const assetId = response.asset.id; console.log(`Asset uploaded successfully! Asset ID: ${assetId}`);}
uploadImage();Response
Section titled “Response”A successful response returns an HTTP 200 OK status code and a JSON object containing details about the uploaded asset:
{ "asset": { "id": "asset_staging_qSACAtKeAbd1EmAEG4YNguea", "url": "https://cdn.cloud.scenario.com/assets-transform/asset_staging_qSACAtKeAbd1EmAEG4YNguea?...", "mimeType": "image/png", "metadata": { "type": "uploaded", "name": "fileName.png", "kind": "image", "width": 1024, "height": 1024, "size": 979892 }, "properties": { "width": 1024, "height": 1024, "size": 979892 }, "kind": "image", "source": "uploaded", "ownerId": "proj_eTyi419soRtdN2AexdFHZDqX", "authorId": "03223725c4b2d23114ab7d673bcbf850", "createdAt": "2025-06-30T15:15:27.153Z", "updatedAt": "2025-06-30T15:15:27.153Z", "privacy": "private", "tags": [], "collectionIds": [], "status": "success", "editCapabilities": [ "REMOVE_BACKGROUND", "REFINE", "PIXELATE", "PROMPT_EDITING", "UPSCALE", "UPSCALE_360", "DETECTION", "VECTORIZATION", "SEGMENTATION", "REFRAME", "GENERATIVE_FILL" ] }}Key fields:
id- The uniqueassetIdfor referencing in other API callsurl- The URL where the asset can be accessedmimeType- The MIME type of the uploaded fileproperties- Image dimensions and size information
Uploading Larger Images, Audio, Video, and 3D Models: /v1/uploads
Section titled “Uploading Larger Images, Audio, Video, and 3D Models: /v1/uploads”The /v1/uploads endpoint uses a multipart upload process for:
- Large images (6MB or larger) that exceed the
/v1/assetssize limit - Audio files (for video lip-sync, music generation, etc.)
- Video files
- 3D models
This endpoint is required for all non-image files and recommended for images that are 6MB or larger.
Supported File Types
Section titled “Supported File Types”Images (Large Files)
Section titled “Images (Large Files)”- JPEG -
image/jpeg - PNG -
image/png - GIF -
image/gif - SVG -
image/svg+xml - WebP -
image/webp - AVIF -
image/avif - TIFF -
image/tiff - HEIF / HEIC -
image/heic
Audio Files
Section titled “Audio Files”- MP3 -
audio/mpegoraudio/mp3 - WAV -
audio/wav - OGG -
audio/ogg - M4A -
audio/m4a
Video Files
Section titled “Video Files”- MP4 -
video/mp4 - WebM -
video/webm
3D Models
Section titled “3D Models”- GLB -
model/gltf-binary - GLTF -
model/gltf+json - OBJ -
model/obj - FBX -
application/vnd.autodesk.fbx - STL -
model/stl - PLY -
model/ply - VOX -
model/x-3d-vox - And other 3D formats
Multipart Upload Process
Section titled “Multipart Upload Process”The /v1/uploads endpoint uses a 4-step multipart upload process:
- Initialize the upload - Create an upload entry and get presigned URLs
- Upload file parts - Upload your file to the presigned URLs
- Complete the upload - Signal that all parts have been uploaded
- Retrieve the assetId - Poll the upload status until processing is complete
Step-by-Step Guide: Multipart Upload
Section titled “Step-by-Step Guide: Multipart Upload”Step 1: Initialize the Upload
Section titled “Step 1: Initialize the Upload”Make a POST request to /v1/uploads with file metadata:
Endpoint: POST /v1/uploads?projectId={projectId}
Request Body (Large Image Example):
{ "fileName": "large-image.png", "contentType": "image/png", "kind": "image", "parts": 1, "fileSize": 8388608}Request Body (Audio Example):
{ "fileName": "audio.mp3", "contentType": "audio/mpeg", "kind": "audio", "parts": 1, "fileSize": 1234567}Request Body (Video Example):
{ "fileName": "video.mp4", "contentType": "video/mp4", "kind": "video", "parts": 1, "fileSize": 5678901}Request Body (3D Model Example):
{ "fileName": "model.glb", "contentType": "model/gltf-binary", "kind": "3d", "parts": 1, "fileSize": 2345678}Parameters:
fileName(required) - The name of your filecontentType(required) - The MIME type of your file:- Images:
image/jpeg,image/png,image/gif, etc. - Audio:
audio/mpeg,audio/wav,audio/ogg,audio/m4a - Video:
video/mp4,video/webm - 3D Models:
model/gltf-binary,model/gltf+json,model/obj, etc.
- Images:
kind(required) - The file type:"image","audio","video", or"3d"parts(required) - Number of parts (use1for files < 5MB)fileSize(required) - Size of the file in bytes
Response:
{ "upload": { "id": "upload_abc123", "status": "pending", "parts": [ { "number": 1, "url": "https://s3.amazonaws.com/...", "expires": "2024-01-01T12:00:00Z" } ], "jobId": "job_xyz789" }}Save the upload.id and the parts array for the next steps.
Step 2: Upload File Parts
Section titled “Step 2: Upload File Parts”For each part in the parts array, make a PUT request to the presigned URL with the file data.
For single-part uploads (files < 5MB):
const file = fs.readFileSync('audio.mp3');const part = upload.parts[0];
await fetch(part.url, { method: 'PUT', body: file});For multi-part uploads (files > 5MB): Split your file into chunks and upload each chunk:
const fileBuffer = fs.readFileSync('large-file.mp3');const partSize = Math.ceil(fileBuffer.length / partsCount);
for (let i = 0; i < partsCount; i++) { const start = i * partSize; const end = Math.min((i + 1) * partSize, fileBuffer.length); const chunk = fileBuffer.slice(start, end); const part = upload.parts[i];
await fetch(part.url, { method: 'PUT', body: chunk });}Important Notes:
- Presigned URLs expire after a certain time (check the
expiresfield) - Each part must be at least 5MB (except the last part)
- Maximum number of parts is 10,000
- Maximum file size is 5TB
Step 3: Complete the Upload
Section titled “Step 3: Complete the Upload”After all parts have been uploaded, signal completion:
Endpoint: POST /v1/uploads/{uploadId}/action?projectId={projectId}
Request Body:
{ "action": "complete"}Response:
{ "upload": { "id": "upload_abc123", "status": "validating", "entityId": null }}The upload status will change from "pending" → "validating" → "validated" → "imported" when processing is complete.
Step 4: Retrieve the Asset ID
Section titled “Step 4: Retrieve the Asset ID”Poll the upload status until the entityId field is populated. This entityId is your assetId that you can use in other API calls.
Endpoint: GET /v1/uploads/{uploadId}?projectId={projectId}
Response (while processing):
{ "upload": { "id": "upload_abc123", "status": "validating", "entityId": null }}Response (when complete):
{ "upload": { "id": "upload_abc123", "status": "imported", "entityId": "asset_xyz789" }}Complete Code Examples
Section titled “Complete Code Examples”Example 1: Upload Image (Simple)
Section titled “Example 1: Upload Image (Simple)”import base64from scenario_sdk import Scenario
def upload_image(file_path, api_key, api_secret): client = Scenario(api_key=api_key, api_secret=api_secret)
with open(file_path, "rb") as f: encoded = base64.b64encode(f.read()).decode("utf-8")
response = client.assets.upload( name=file_path.split("/")[-1], image=encoded, )
return response.asset.id
# Usageasset_id = upload_image("./image.png", "key_...", "secret_...")print(f"Image uploaded! Asset ID: {asset_id}")Example 2: Upload Large Image
Section titled “Example 2: Upload Large Image”import osimport timeimport requestsfrom scenario_sdk import Scenario
def upload_large_image(file_path, api_key, api_secret): """Upload an image that is 6MB or larger using multipart upload.""" client = Scenario(api_key=api_key, api_secret=api_secret)
file_size = os.path.getsize(file_path) file_name = os.path.basename(file_path)
# Determine content type based on extension ext = file_name.split('.')[-1].lower() content_types = { 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'png': 'image/png', 'gif': 'image/gif', 'webp': 'image/webp', 'svg': 'image/svg+xml' } content_type = content_types.get(ext, 'image/jpeg')
# Step 1: Initialize upload init_response = client.uploads.create( file_name=file_name, content_type=content_type, kind="image", parts=1, file_size=file_size, ) upload = init_response.upload upload_id = upload.id
# Step 2: Upload file part to presigned URL with open(file_path, 'rb') as f: image_data = f.read()
part = upload.parts[0] requests.put(part.url, data=image_data).raise_for_status()
# Step 3: Complete upload client.uploads.trigger_action(upload_id, action="complete")
# Step 4: Wait for processing and get assetId while True: time.sleep(3) status_response = client.uploads.retrieve(upload_id) upload_status = status_response.upload
if upload_status.status == "imported" and upload_status.entity_id: return upload_status.entity_id elif upload_status.status == "failed": raise Exception(f"Upload failed: {upload_status.error_message}")
# Usageasset_id = upload_large_image('./large-image.png', 'key_...', 'secret_...')print(f"Large image uploaded! Asset ID: {asset_id}")Example 3: Upload Audio for VEED Fabric
Section titled “Example 3: Upload Audio for VEED Fabric”import osimport timeimport requestsfrom scenario_sdk import Scenario
def upload_audio_file(file_path, api_key, api_secret): client = Scenario(api_key=api_key, api_secret=api_secret)
file_size = os.path.getsize(file_path) file_name = os.path.basename(file_path)
# Determine content type ext = file_name.split('.')[-1].lower() content_types = { 'mp3': 'audio/mpeg', 'wav': 'audio/wav', 'ogg': 'audio/ogg', 'm4a': 'audio/m4a' } content_type = content_types.get(ext, 'audio/mpeg')
# Step 1: Initialize upload init_response = client.uploads.create( file_name=file_name, content_type=content_type, kind="audio", parts=1, file_size=file_size, ) upload = init_response.upload upload_id = upload.id
# Step 2: Upload file part to presigned URL with open(file_path, 'rb') as f: audio_data = f.read()
part = upload.parts[0] requests.put(part.url, data=audio_data).raise_for_status()
# Step 3: Complete upload client.uploads.trigger_action(upload_id, action="complete")
# Step 4: Wait for processing and get assetId while True: time.sleep(3) status_response = client.uploads.retrieve(upload_id) upload_status = status_response.upload
if upload_status.status == "imported" and upload_status.entity_id: return upload_status.entity_id elif upload_status.status == "failed": raise Exception(f"Upload failed: {upload_status.error_message}")
# Usageasset_id = upload_audio_file('./audio.mp3', 'key_...', 'secret_...')print(f"Audio uploaded! Asset ID: {asset_id}")
# Use with VEED Fabricclient = Scenario(api_key="key_...", api_secret="secret_...")response = client.generate.create( model_id="veed-fabric-1.0", parameters={ "audioUrl": asset_id, # ... other parameters },)Example 4: Generic File Upload Function (TypeScript)
Section titled “Example 4: Generic File Upload Function (TypeScript)”import Scenario from '@scenario-labs/sdk';import type { UploadCreateParams } from '@scenario-labs/sdk/resources/uploads';import fs from 'fs';
const client = new Scenario({ apiKey: 'YOUR_API_KEY', apiSecret: 'YOUR_API_SECRET',});
async function uploadFile( filePath: string, kind: UploadCreateParams['kind'], contentType: string,): Promise<string> { const file = fs.readFileSync(filePath); const fileSize = fs.statSync(filePath).size; const fileName = filePath.split('/').pop()!;
// Step 1: Initialize upload const { upload } = await client.uploads.create({ fileName, contentType, kind, parts: 1, fileSize, }); const uploadId = upload.id;
// Step 2: Upload file part to presigned URL const part = upload.parts![0]; await fetch(part.url, { method: 'PUT', body: file, });
// Step 3: Complete upload await client.uploads.triggerAction(uploadId, { action: 'complete' });
// Step 4: Wait for processing and get assetId let assetId: string | null = null; while (!assetId) { await new Promise((resolve) => setTimeout(resolve, 3000));
const { upload: uploadStatus } = await client.uploads.retrieve(uploadId);
if (uploadStatus.status === 'imported' && uploadStatus.entityId) { assetId = uploadStatus.entityId; } else if (uploadStatus.status === 'failed') { throw new Error(`Upload failed: ${uploadStatus.errorMessage}`); } }
return assetId;}
// Usage examples:(async () => { // Upload large image (6MB+) const largeImageAssetId = await uploadFile('./large-image.png', 'image', 'image/png'); console.log('Large image assetId:', largeImageAssetId);
// Upload audio const audioAssetId = await uploadFile('./audio.mp3', 'audio', 'audio/mpeg'); console.log('Audio assetId:', audioAssetId);
// Upload video const videoAssetId = await uploadFile('./video.mp4', 'video', 'video/mp4'); console.log('Video assetId:', videoAssetId);
// Upload 3D model const modelAssetId = await uploadFile('./model.glb', '3d', 'model/gltf-binary'); console.log('3D model assetId:', modelAssetId);})();Common Issues and Solutions
Section titled “Common Issues and Solutions”Issue: “Either ‘image’ or ‘canvas’ must be provided”
Problem: You’re trying to upload audio, video, or 3D models to /v1/assets, which only accepts images. Or your image is too large (> 6MB).
Solution:
- For images 6MB or larger: Use
/v1/uploadswithkind: "image" - For audio, video, or 3D models: Use
/v1/uploadswith the appropriatekind("audio","video", or"3d")
Issue: “Unhandled image format”
Problem: You’re trying to upload a non-image file (audio, video, 3D model) as an image, or your image is too large.
Solution:
- For small images (< 6MB): Use
/v1/assetswith base64 encoded image - For large images (≥ 6MB): Use
/v1/uploadswithkind: "image"and the correctcontentType - For other files: Use
/v1/uploadswith the correctkindandcontentType
Issue: “Missing required mimeType parameter for asset kind audio/video/3d”
Problem: You’re trying to pass an external URL directly to a parameter that expects an assetId.
Solution: Upload the file first using /v1/uploads to get an assetId, then use that assetId in your API call.
Issue: Upload status stuck at “validating”
Problem: The file might be corrupted or in an unsupported format.
Solution:
- Verify the file is valid
- Check that the
contentTypematches the actual file format - Ensure the file was uploaded completely (all parts uploaded successfully)
Issue: Presigned URL expired
Problem: You waited too long between getting the presigned URLs and uploading the file.
Solution: Upload the file parts immediately after receiving the presigned URLs. If needed, re-initialize the upload to get fresh URLs.
Upload Status Values
Section titled “Upload Status Values”For multipart uploads (/v1/uploads), the status progresses through these values:
pending- Upload initialized, waiting for parts to be uploadedvalidating- All parts uploaded, file is being validatedvalidated- File validated successfully, processingimported- Upload complete,entityId(assetId) is availablefailed- Upload failed, checkerrorMessagefor details
Summary
Section titled “Summary”For Small Images (< 6MB):
Section titled “For Small Images (< 6MB):”- ✅ Use
POST /v1/assetswith base64 encoded image - ✅ Include
imageandnamein the request body - ✅ Get
assetIdimmediately from the response
For Large Images (≥ 6MB), Audio, Video, and 3D Models:
Section titled “For Large Images (≥ 6MB), Audio, Video, and 3D Models:”- ✅ Use
POST /v1/uploads(not/v1/assets) - ✅ Set the correct
kind("image","audio","video", or"3d") and matchingcontentType - ✅ Upload file parts to presigned URLs
- ✅ Complete the upload with
action: "complete" - ✅ Poll until
status: "imported"and retrieveentityId(this is yourassetId)
Key Points:
/v1/assetsis for small images only (< 6MB) - simple, single-request upload/v1/uploadsis for large images (≥ 6MB), audio, video, and 3D models - multipart upload process- Both endpoints return an
assetIdthat can be used in other API calls - The
assetIdfrom either endpoint works the same way in subsequent API requests - For images, choose the endpoint based on file size: use
/v1/assetsfor small files,/v1/uploadsfor large files