APIUploadWebDAV

Upload Videos via API: curl, Python, Node.js, WebDAV and FTP

Upload and import videos with the UFOLOAD REST API using curl, Python and Node.js, wait for encoding, and use WebDAV or FTP for large files.

You can put a video on UFOLOAD without ever opening the dashboard. The REST API accepts uploads over HTTPS, the server can fetch a file from a link for you, and the same account is reachable over WebDAV and FTP for tools that already speak those protocols. This guide goes through all five routes with working commands, explains what the server answers, and shows how to wait for encoding to finish. The endpoints below were checked against the public OpenAPI specification; the API documentation has the full reference.

Create an API key

Create a key in the Developers section of your dashboard. Every request carries it as a bearer token in the Authorization header. The key is displayed only once when it is created, so store it in an environment variable or a secrets manager and revoke it from the dashboard if it leaks. The same key works as the password for WebDAV and FTP, with your account email as the login. If you do not have an account yet, create a free one; the pricing page lists the file size limit and the storage and traffic quotas that apply to your plan.

bash
export UFOLOAD_API_KEY="ufo_..."

Upload with curl

The simplest upload sends the file as the raw request body and puts its name into the X-File-Name header. Send the name percent-encoded (UTF-8) if it contains spaces or non-ASCII characters. By default the file lands in the root folder; add ?folder_id= or an X-Folder-Id header to choose another one.

Raw body · bash
curl -X POST "https://ufoload.com/api/v1/files?folder_id=root" \
  -H "Authorization: Bearer $UFOLOAD_API_KEY" \
  -H "X-File-Name: holiday-2026.mp4" \
  -H "Content-Type: video/mp4" \
  --data-binary @holiday-2026.mp4

A multipart form works as well. The file goes into the file field and the folder can be given as a folder_id form field:

Multipart form · bash
curl -X POST "https://ufoload.com/api/v1/files" \
  -H "Authorization: Bearer $UFOLOAD_API_KEY" \
  -F "file=@holiday-2026.mp4"

A successful upload answers 201 with a JSON body that contains the file object. The fields you will use most are id, public_id (the short code that goes into player addresses), status, duration_ms and max_quality. The server does not use extra space for content it already has: if you upload the same bytes again, it answers 200 with the existing file and "duplicate": true, matching files by their SHA-256 hash.

StatusMeaning
201The file was accepted and queued for processing
200The same content is already in your library; duplicate is true
401The key is missing or invalid
404The destination folder does not exist
409Not enough storage
413The file is larger than your plan allows
429Too many requests; wait and retry
Tip

Large uploads are not subject to the short request timeouts that apply to ordinary API calls. The server accepts the body for as long as it keeps arriving, so a slow connection does not break a multi-gigabyte upload, though a pause between blocks that is too long can still be cut by the reverse proxy.

Wait until the video is ready

Uploading is only the first half. After it, the file is queued and encoded into an adaptive HLS ladder (see how the ladder is built). Its status moves through uploading, queued and processing and ends at ready or failed; a failed file carries an error message. Poll the file every few seconds, and never in a tight loop: the API is rate limited and answers 429.

bash
curl "https://ufoload.com/api/v1/files/FILE_ID" \
  -H "Authorization: Bearer $UFOLOAD_API_KEY"

The answer has two parts: file with the metadata, and links with signed addresses for the master playlist (playback), the cover (poster), the hover-preview sprite (sprite), every rendition and expires_at. If you need a different lifetime, POST /files/{id}/links issues links valid from 60 seconds to 30 days, optionally bound to an IP address or a cookie.

Python

With the requests library, passing the open file object streams it from disk, so memory stays flat even for large videos. The function below uploads, then waits for the result.

python
import os
import time
import urllib.parse

import requests

BASE = "https://ufoload.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['UFOLOAD_API_KEY']}"}


def upload(path, folder_id="root"):
    name = urllib.parse.quote(os.path.basename(path))
    with open(path, "rb") as f:
        r = requests.post(
            f"{BASE}/files",
            params={"folder_id": folder_id},
            data=f,
            headers={**HEADERS, "X-File-Name": name, "Content-Type": "application/octet-stream"},
        )
    r.raise_for_status()
    return r.json()["file"]


def wait_ready(file_id, timeout=3600):
    deadline = time.time() + timeout
    while time.time() < deadline:
        r = requests.get(f"{BASE}/files/{file_id}", headers=HEADERS)
        r.raise_for_status()
        data = r.json()
        status = data["file"]["status"]
        if status == "ready":
            return data
        if status == "failed":
            raise RuntimeError(data["file"].get("error") or "encoding failed")
        time.sleep(5)
    raise TimeoutError("video is still processing")


file = upload("holiday-2026.mp4")
data = wait_ready(file["id"])
print("embed:", f"https://ufoload.com/e/{file['public_id']}")
print("playlist:", data["links"]["playback"])

Node.js

Node.js 18 and newer has fetch built in, so no dependencies are needed. This version reads the file into memory, which is fine for clips and short videos; for multi-gigabyte files prefer curl, WebDAV or FTP, which stream from disk.

js
import { readFile } from 'node:fs/promises'
import { basename } from 'node:path'
import { setTimeout as sleep } from 'node:timers/promises'

const BASE = 'https://ufoload.com/api/v1'
const headers = { Authorization: `Bearer ${process.env.UFOLOAD_API_KEY}` }

async function upload(path) {
  const res = await fetch(`${BASE}/files?folder_id=root`, {
    method: 'POST',
    headers: { ...headers, 'X-File-Name': encodeURIComponent(basename(path)), 'Content-Type': 'application/octet-stream' },
    body: await readFile(path),
  })
  if (!res.ok) throw new Error(`upload failed: ${res.status}`)
  return (await res.json()).file
}

async function waitReady(id) {
  for (;;) {
    const res = await fetch(`${BASE}/files/${id}`, { headers })
    if (!res.ok) throw new Error(`status check failed: ${res.status}`)
    const data = await res.json()
    if (data.file.status === 'ready') return data
    if (data.file.status === 'failed') throw new Error(data.file.error || 'encoding failed')
    await sleep(5000)
  }
}

const file = await upload('holiday-2026.mp4')
const data = await waitReady(file.id)
console.log('embed:', `https://ufoload.com/e/${file.public_id}`)
console.log('playlist:', data.links.playback)

Import by URL

If the video is already online at a direct link, let the server download it instead of pulling it to your machine and pushing it again. The call returns 202 right away with an import task; the server fetches the file and then queues it for encoding. Links to internal networks, loopback and service address ranges are rejected, you can have up to ten active imports, and GET /files/import shows progress.

bash
curl -X POST "https://ufoload.com/api/v1/files/import" \
  -H "Authorization: Bearer $UFOLOAD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/video.mp4", "name": "video.mp4"}'

WebDAV and FTP

For bulk transfers and for tools that cannot call an API, the same library is available over WebDAV and FTP. The login is your account email and the password is an API key. Both stream from disk and are the most robust option for very large files.

WebDAV over HTTPS, port 443 · bash
curl -u "you@example.com:$UFOLOAD_API_KEY" https://ufoload.com/dav/
curl -u "you@example.com:$UFOLOAD_API_KEY" -T holiday-2026.mp4 https://ufoload.com/dav/holiday-2026.mp4
FTP with explicit TLS, port 21 · bash
curl -u "you@example.com:$UFOLOAD_API_KEY" ftp://ufoload.com/
curl -u "you@example.com:$UFOLOAD_API_KEY" --ftp-ssl -T holiday-2026.mp4 ftp://ufoload.com/

Use the encrypted variants whenever your client supports them, since the API key is the password. Any client that speaks WebDAV or FTPS, from file managers to sync tools, can use the same details. Files copied this way appear in your library and are encoded like any other upload.

Which route to choose

RouteBest forWatch out for
API uploadApplications and backends that upload on behalf of usersMemory use if the client reads the whole file first
Import by URLFiles that already have a public direct linkOnly direct http(s) links; limit of ten active imports
WebDAVLarge files, mounting the library as a driveUse your email and API key as credentials
FTP / FTPSExisting tools and scripts, bulk transferUse the explicit TLS option

Use the video

Once the status is ready, the player address is https://ufoload.com/e/ followed by public_id, which is exactly what you put in an iframe (the long id works too and is redirected to the short address); the embedding guide shows the markup and the hls.js alternative. You can restrict where it plays with domain and country rules, and enable ads to earn from views. Keep the key out of browser code, rotate it when people leave your team, and remember that the API is available on every plan, so you can start building today.

Try it with a free account

Upload a video, get an HLS player and an API key in minutes. Plan limits are listed on the pricing page.