Skip to content

API

o/DAILIES provides a RESTful API that allows you to:

  1. Read and write clip metadata
  2. 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}
Response
{
  "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"
  }
}