API
o/DAILIES provides a RESTful API that allows you to:
- Read and write clip metadata
- Upload media files
Authentication
All API endpoints require an API token for authentication. You can create tokens in the Settings page of the o/DAILIES web app. Each token can have the following permissions:
- Read: Allows reading clip metadata
- Write: Allows updating clip metadata
- Upload: Allows uploading media files
Endpoints
Base URL: https://odailies.com/api/v1
Metadata API
Endpoint: /clips
Get Clip Metadata
GET /clips/{project_id}/{token}
Retrieves metadata for all clips in the project.
Response
{
"data": {
"message": {
"clip_name": {
"name": "string",
"scene": "string",
"shot": "string",
"take": "string",
"description": "string",
"circled": boolean,
"rating": number,
"inPoint": number,
"camera": "string",
"timecode": "string",
"fps": number,
"date": "string",
"duration": number,
"reelname": "string",
"whitepointkelvin": number,
"whitepointccshift": number,
"shutter": number,
"filter": "string",
"ndfilter": "string",
"lens": "string",
"focallength": number,
"focusdistance": number,
"asa": number,
"sensorFps": number,
"fStop": number,
"tStop": number,
"filesize": number,
"librarypath": "string"
}
}
}
}
Update Clip Metadata
PATCH /clips/{project_id}/{token}
Content-Type: application/json
{
"data": [
{
"name": "A001C001_220901_R3CK",
"scene": "22",
"shot": "4",
"take": "1",
},
{
"name": "A001C002_220901_R3CK",
"scene": "22",
"shot": "4",
"take": "2",
"circled": true,
}
]
}
Updates metadata for specified clips. Only the fields you include will be updated. Each clip in the list must include a name
field to identify the clip to update.
Upload API
Endpoint: /upload
Get Upload URL
GET /upload/{project_id}/{token}/{path}
{
"url": "string", // Signed URL for uploading
"expiresAt": "2024-12-30T08:34:05Z"
}
Generates a signed URL valid for 15 minutes. The expiresAt
field indicates when the URL will expire in ISO 8601 format (UTC).
Note
If an upload has started and transferred at least ~100KB before expiration, it will be allowed to complete.
Info
Make sure your video files meet our specifications before uploading.
Available Directories
The path
parameter should follow the o/DAILIES directory structure:
_graded/
: For your graded dailies clips_log/
: For LOG variants of clips - allows users with "Advanced Video options" to switch video sources (learn more)_thumbnails/
: For thumbnail images (.jpg
files)
See the uploading documentation for more details about file handling.
File Naming
o/DAILIES uses filenames as unique identifiers to link related files together. All files related to the same clip must share the same base filename:
- The graded clip in
_graded/
defines the base filename - Its LOG variant in
_log/
must use the exact same filename - Its thumbnail in
_thumbnails/
must use the same filename (with.jpg
extension) - Any metadata (like ALE files) must reference the same filename
Example:
_graded/A001C001_220901_R3CK.mp4
_log/A001C001_220901_R3CK.mp4
_thumbnails/A001C001_220901_R3CK.jpg
Supported File Types
.mp4
: Video files (in_graded/
or_log/
) - see video file specifications.jpg
: Thumbnail images (in_thumbnails/
).ale
: Metadata files (processed and automatically removed after import)
Automatic Thumbnails
When you upload a file to _graded/
, a thumbnail is automatically extracted from the middle frame of the video.
You only need to upload a custom thumbnail to _thumbnails/
if you want to override the automatically generated one.
Code Example (Python)
import requests
import json
class oDailiesClient:
def __init__(self, project_id: str, token: str):
self.project_id = project_id
self.token = token
self.base_url = f"https://odailies.com/api/v1"
self.metadata_endpoint = f"{self.base_url}/clips"
self.upload_endpoint = f"{self.base_url}/upload"
def get_clips(self) -> dict:
"""Get metadata for all clips"""
response = requests.get(f"{self.metadata_endpoint}/{self.project_id}/{self.token}")
response.raise_for_status()
return response.json()["data"]["message"]
def update_clip(self, clip_name: str, metadata: dict):
"""Update metadata for a clip"""
response = requests.patch(
f"{self.metadata_endpoint}/{self.project_id}/{self.token}",
json={"data": [{"name": clip_name, **metadata}]},
headers={"Content-Type": "application/json"}
)
response.raise_for_status()
return response.json()
def update_clips(self, clips: list):
"""Update metadata for multiple clips"""
response = requests.patch(
f"{self.metadata_endpoint}/{self.project_id}/{self.token}",
json={"data": clips},
headers={"Content-Type": "application/json"}
)
response.raise_for_status()
return response.json()
def upload_file(self, file_path: str, destination: str):
"""Upload a file using signed URL"""
# Get upload URL
response = requests.get(
f"{self.upload_endpoint}/{self.project_id}/{self.token}/{destination}"
)
response.raise_for_status()
upload_url = response.json()["url"]
# Upload file
with open(file_path, "rb") as f:
response = requests.put(
upload_url,
data=f,
headers={"Content-Type": "application/octet-stream"}
)
response.raise_for_status()
# Example usage
client = oDailiesClient("your_project_id", "your_token")
# Get all clips
clips = client.get_clips()
print(json.dumps(clips, indent=2))
# Update multiple clips
client.update_clips([
{
"name": "A001C001_220901_R3CK",
"scene": "22",
"shot": "4",
"take": "1",
},
{
"name": "A001C002_220901_R3CK",
"scene": "22",
"shot": "4",
"take": "2",
"circled": True,
},
])
# Upload a graded file
client.upload_file("A001C001_220901_R3CK.mp4", "_graded/A001C001_220901_R3CK.mp4")
# Upload its LOG variant (must use same filename)
client.upload_file("A001C001_220901_R3CK.mp4", "_log/A001C001_220901_R3CK.mp4")
# Upload its thumbnail (must use same filename)
client.upload_file("A001C001_220901_R3CK.jpg", "_thumbnails/A001C001_220901_R3CK.jpg")
# Upload metadata
client.upload_file("metadata.ale", "metadata.ale") # Will be processed and removed
Error Handling
The API uses standard HTTP status codes:
- 200: Success
- 400: Bad Request (invalid URL format, missing parameters)
- 401: Unauthorized (invalid token)
- 403: Forbidden (insufficient permissions)
- 405: Method Not Allowed
- 500: Internal Server Error
Error responses include a message in the following format:
{
"error": {
"message": "Error description"
}
}