Skip to content
Last updated

Local Video Upload Service

Upload a local video file and use it as input to any Bria video editing endpoint.

Most Bria video endpoints accept a video parameter that points to a publicly accessible URL. When your video lives on your own machine rather than at a public URL, use the upload service to get a temporary, hosted URL you can pass straight into those endpoints.

The flow is three steps:

  1. Request a presigned upload URL — call POST /v2/video/upload to get an upload_url, the upload_fields needed to authenticate the upload, and the file_url your video will live at.
  2. Upload the filePOST the video directly to upload_url as multipart form data.
  3. Process the video — pass file_url as the video input to any video editing endpoint.

Using the Python SDK? The Bria Python client wraps this whole flow — see the SDK docs for the one-call helper. The instructions below describe the underlying HTTP API for everyone else.


Step 1 — Request a presigned upload URL

POST /v2/video/upload

Body (JSON)

FieldTypeRequiredDescription
media_typestringNoMIME type of the video you intend to upload, e.g. "video/mp4". When omitted, the server falls back to a "video/" content-type prefix.
{ "media_type": "video/mp4" }

An empty body ({}) is also valid.

Response (result)

FieldTypeDescription
upload_urlstringPresigned URL to upload your video to. Valid for 1 hour.
upload_fieldsdictPresigned form fields (key, policy, signature, etc.) that must be included in the upload POST to authenticate it.
file_urlstringThe URL your video will be available at after upload. Pass this as the video input to any video endpoint. Valid for 1 day.

Step 2 — Upload the file

POST the video to upload_url as multipart/form-data. The upload_fields must be sent as form fields before the file field — they are validated in order, so the actual file must come last.

with open(video_path, "rb") as f:
    # All presigned fields first, then the file last
    files = {k: (None, v) for k, v in upload_fields.items()}
    files["file"] = (video_path, f, "video/mp4")

    resp = requests.post(upload_url, files=files)

A successful upload returns HTTP 204 No Content.


Step 3 — Process the video

Pass file_url as the video input to any video editing endpoint.

POST /v2/video/edit/remove_background
{ "video": "<file_url>" }

Video editing endpoints are asynchronous: a successful call returns HTTP 202 with a request_id and a status_url. Poll status_url until status is COMPLETED (or FAILED); the finished video is then available at result.video_url.

{
  "request_id": "04e104bab7e44319bbf2427d64d8c313",
  "status_url": "https://engine.prod.bria-api.com/v2/status/04e104bab7e44319bbf2427d64d8c313"
}

Limits & expiry

ConstraintValue
Content typeVideo files only
Max file size1 GB
upload_url validity1 hour
file_url validity1 day

Security note: Both upload_url and file_url are unauthenticated — anyone holding either can upload to or download from your file. Treat them as secrets and don't share them beyond your processing pipeline.


Full example

This script runs the complete flow: request a presigned URL, upload a local video, submit it to remove_background, and poll until the output video is ready.

import time
import requests

BASE_URL = "https://engine.prod.bria-api.com"
API_TOKEN = "<YOUR_API_TOKEN>"
HEADERS = {"api_token": API_TOKEN}


def get_presigned_url(media_type: str = "video/mp4") -> dict:
    """Step 1 — request a presigned POST URL to upload the video."""
    resp = requests.post(
        f"{BASE_URL}/v2/video/upload",
        json={"media_type": media_type},
        headers=HEADERS,
    )
    resp.raise_for_status()
    return resp.json()["result"]


def upload_video(upload_url: str, upload_fields: dict, video_path: str) -> None:
    """Step 2 — upload the video directly using the presigned POST URL.

    The upload_fields (key, policy, signature, etc.) must be sent as
    multipart form fields *before* the file field — they are validated
    in order, so the file must come last.
    """
    with open(video_path, "rb") as f:
        files = {k: (None, v) for k, v in upload_fields.items()}
        files["file"] = (video_path, f, "video/mp4")
        resp = requests.post(upload_url, files=files)

    # A presigned POST returns 204 No Content on success
    if resp.status_code != 204:
        raise RuntimeError(f"Upload failed: HTTP {resp.status_code}\n{resp.text}")


def submit_remove_background(file_url: str) -> str:
    """Step 3a — submit the uploaded video to a video editing endpoint.

    Video editing endpoints are async — this returns a status_url to poll.
    """
    resp = requests.post(
        f"{BASE_URL}/v2/video/edit/remove_background",
        json={"video": file_url},
        headers=HEADERS,
    )
    resp.raise_for_status()
    return resp.json()["status_url"]


def wait_for_result(status_url: str, interval: float = 5.0, timeout: float = 600.0) -> dict:
    """Step 3b — poll status_url until the job is COMPLETED or FAILED."""
    deadline = time.monotonic() + timeout
    while time.monotonic() < deadline:
        resp = requests.get(status_url, headers=HEADERS)
        resp.raise_for_status()
        data = resp.json()
        if data["status"] == "COMPLETED":
            return data["result"]
        if data["status"] in ("FAILED", "ERROR"):
            raise RuntimeError(f"Job failed: {data}")
        time.sleep(interval)
    raise TimeoutError(f"Job did not complete within {timeout}s")


def main():
    video_path = "path/to/video.mp4"

    upload     = get_presigned_url()
    upload_video(upload["upload_url"], upload["upload_fields"], video_path)
    status_url = submit_remove_background(upload["file_url"])
    result     = wait_for_result(status_url)

    print(result["video_url"])


if __name__ == "__main__":
    main()