Simplifyd CloudSimplifyd Cloud

Videos

Upload videos, check processing status, and retrieve playback links and embed code.

Video libraries are currently in Beta. There are limits on how much you can store, and features and pricing may change.

A video library stores videos and prepares them for playback at multiple resolutions. Once processing is complete, each video has playback links and embed code.

On supported mobile networks, playback data charges are billed to your workspace instead of the viewer. Viewers on these networks can watch without a data balance.

Use edge video to upload and manage videos from your terminal or scripts.

edge video upload talk.mp4 --library training

Before you start

Create the library first. You create a video library in the web console, on your project canvas. The CLI works with a library that already exists; it does not make one.

Set your workspace, project and environment. Every command below requires all three. Set them for the current directory:

edge link

Or pass them on each command with -w, -p and -e.

Specify the library. Every command requires --library (short: -l), set to the name or ID of the target video library.

edge video list --library training
edge video list -l training

upload

Upload a video file and start preparing it for playback.

edge video upload <file> --library <library> [flags]

Uploads use multiple parts. Failed parts are retried individually; use --concurrency and --retries to control this behaviour.

Flags

FlagDescription
--titleTitle for the video. Defaults to the file name without its extension.
--waitWait until the video is ready to play before the command returns.
--concurrencyHow many parts to send at once. Default 4.
--retriesHow many times to try a part before giving up. Default 5.
--resumeFinish an upload that was cut short. See If an upload is interrupted.

Accepted files: .mp4, .mov, .mkv, .webm and .m4v.

Examples

# Upload with the file name as the title
edge video upload talk.mp4 -l training

# Give it a proper title
edge video upload talk.mp4 -l training --title "Opening the account"

# Wait until it is ready, then print the embed code
edge video upload talk.mp4 -l training --wait
edge video embed <video-id> -l training

Example output

  [============================] 100.0%  1.4 GiB / 1.4 GiB
✓ Uploaded Opening the account

Field    Value
ID       0192f3c1-8a44-7c31-9d10-6b2e4f9a1c77
Status   queued
Source   1.4 GiB

Encoding has been queued. Follow it with: edge video get 0192f3c1-8a44-7c31-9d10-6b2e4f9a1c77

The progress bar appears only in an interactive terminal. It is omitted in scripts and with --json.

JSON output

edge video upload talk.mp4 -l training --json
{
  "slug": "0192f3c1-8a44-7c31-9d10-6b2e4f9a1c77",
  "title": "Opening the account",
  "status": "queued",
  "source_bytes": 1503238553,
  "has_source": true,
  "created_at": "2026-09-04T09:12:44Z"
}

The links and the embed snippet appear once the status is ready. Add --wait if you want the command to return only at that point.


If an upload is interrupted

Completed parts are retained after an interrupted upload. The command prints a resume command:

3 of 214 parts are stored. Carry on with:
  edge video upload ./talk.mp4 --library training --resume 0192f3c1-8a44-7c31-9d10-6b2e4f9a1c77

Run the resume command to upload only the missing parts.

Two things to know:

  • Use the same file. The size is checked against what the upload started with, so you cannot accidentally finish one upload with a different file.
  • Unfinished uploads remain listed. It shows in edge video list with the status uploading. Either finish it with --resume, or remove it with edge video rm.

Calling it from your application

edge video upload --json prints the video as JSON on stdout and nothing else, which makes it usable as a subprocess wherever the file is already on disk — a queue worker, a build step, a backend handling an upload from your own users.

Set CLOUD_TOKEN and pass the context on the command line, so the behaviour does not depend on which directory the process happens to run in:

import { execFile } from 'node:child_process';
import { promisify } from 'node:util';

const run = promisify(execFile);

export async function uploadVideo(path: string, title: string) {
  const { stdout } = await run(
    'edge',
    [
      'video', 'upload', path,
      '--library', 'training',
      '--title', title,
      '--json',
      '-w', process.env.SIMPLIFYD_WORKSPACE!,
      '-p', process.env.SIMPLIFYD_PROJECT!,
      '-e', process.env.SIMPLIFYD_ENV!,
    ],
    { env: { ...process.env, CLOUD_TOKEN: process.env.SIMPLIFYD_API_TOKEN } },
  );

  return JSON.parse(stdout);
}

execFile, not exec: it passes the arguments as a list rather than through a shell, so a title with a quote or a semicolon in it is a title rather than a second command.

<?php

function uploadVideo(string $path, string $title): array
{
    $command = [
        'edge', 'video', 'upload', $path,
        '--library', 'training',
        '--title', $title,
        '--json',
        '-w', getenv('SIMPLIFYD_WORKSPACE'),
        '-p', getenv('SIMPLIFYD_PROJECT'),
        '-e', getenv('SIMPLIFYD_ENV'),
    ];

    $descriptors = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
    $process = proc_open(
        $command,
        $descriptors,
        $pipes,
        null,
        ['CLOUD_TOKEN' => getenv('SIMPLIFYD_API_TOKEN')] + $_ENV,
    );

    $stdout = stream_get_contents($pipes[1]);
    $stderr = stream_get_contents($pipes[2]);
    array_map('fclose', $pipes);

    if (proc_close($process) !== 0) {
        throw new RuntimeException(trim($stderr));
    }

    return json_decode($stdout, true);
}

Pass the command as an array. proc_open then runs the binary directly instead of handing the string to a shell, and a filename with a space in it stays one argument.

// The fields worth naming from the CLI's JSON. It prints the same video
// document the API returns, so add fields as you need them.
type Video struct {
	Slug        string `json:"slug"`
	Title       string `json:"title"`
	Status      string `json:"status"`
	SourceBytes int64  `json:"source_bytes"`
}

func UploadVideo(ctx context.Context, path, title string) (*Video, error) {
	cmd := exec.CommandContext(ctx, "edge",
		"video", "upload", path,
		"--library", "training",
		"--title", title,
		"--json",
		"-w", os.Getenv("SIMPLIFYD_WORKSPACE"),
		"-p", os.Getenv("SIMPLIFYD_PROJECT"),
		"-e", os.Getenv("SIMPLIFYD_ENV"),
	)
	cmd.Env = append(os.Environ(), "CLOUD_TOKEN="+os.Getenv("SIMPLIFYD_API_TOKEN"))

	// Only stdout carries the document. Progress and errors go to stderr, and
	// mixing them into the same buffer would leave you parsing a progress bar.
	var stdout, stderr bytes.Buffer
	cmd.Stdout, cmd.Stderr = &stdout, &stderr

	if err := cmd.Run(); err != nil {
		return nil, fmt.Errorf("edge video upload: %s", strings.TrimSpace(stderr.String()))
	}

	var video Video
	if err := json.Unmarshal(stdout.Bytes(), &video); err != nil {
		return nil, err
	}
	return &video, nil
}

Go applications can use the Go SDK directly. It supports multipart uploads, retries, and resuming interrupted uploads:

client := cloud.NewClient(cloud.WithToken(os.Getenv("SIMPLIFYD_API_TOKEN")))
videos := client.Workspace(ws).Project(proj).Env(env).Services().Video(library)

video, err := videos.Upload(ctx, path, title, nil)

Choosing an interface

The CLI as a subprocessThe file is on disk, edge is installed, and you want parts, retries and --resume without writing them. Good in CI and in workers you control.
The HTTP APINothing to install, and the file need never touch a disk — a request handler can pass an upload straight through. Capped at 300 MiB per request.
The Go SDKYou are writing Go. Everything the CLI does, as function calls.

Video IDs can be used across the CLI, HTTP API, and Go SDK.


list

List the videos in a library, newest first.

edge video list -l training
edge video list -l training --limit 200

Flags

FlagDescription
--limitMost videos to return. Default 50.
--offsetHow many to skip, for paging. Default 0.

Example output

ID                                    TITLE                  STATUS          LENGTH   STORED
0192f3c1-8a44-7c31-9d10-6b2e4f9a1c77  Opening the account    ready           12:04    286.4 MiB
0192f3bd-1c02-7a55-b3e9-77c1de40aa31  Card replacement       processing 62%  —        0 B
0192f3b8-9d71-70e2-84cc-2f0b6a51f4d2  Lost PIN               failed: this…   —        0 B

3 of 3

What the statuses mean

StatusMeaning
uploadingThe file has not finished arriving. Resume it, or remove it.
queuedUpload complete; waiting for processing.
processingProcessing in progress, with a completion percentage.
readyPlayback links and embed code are available.
failedProcessing failed. The error message describes the cause.

get

Show a video's status, available resolutions, captions, and playback links.

edge video get <video-id> -l training

For failed videos, the response includes an error message, such as an unsupported file format.


embed

Print the snippet to put the video on a page.

edge video embed <video-id> -l training

The snippet is printed on its own, with nothing around it, so you can pipe it straight into a template:

edge video embed <video-id> -l training >> page.html

Use --inline for the version that places the player inside your own page markup, rather than in a frame of its own. The default iframe embed isolates the player from the surrounding page styles.

A video only has an embed once it is ready.


Embed options

Both iframe and inline embeds support per-embed playback options.

On the frame embed

Options go on the URL in src:

<iframe src="https://<library>.video.simplifyd.app/e/<video-id>?t=90&autoplay=1"
        width="640" height="360" loading="lazy"
        allow="fullscreen; picture-in-picture; clipboard-write"
        style="border:0;aspect-ratio:16/9;width:100%;height:auto"></iframe>
ParameterWhat it does
tStart somewhere other than the beginning. Takes seconds (90), a clock reading (1:30), or 1m30s.
autoplay=1Start playing as soon as the page loads. The player mutes itself to do it, because muted is the only autoplay a browser allows; the viewer can unmute.

t is what the player's own Copy link at current time produces, so a link someone sends you from a video is one of these.

Keep the generated allow attribute, including clipboard-write, so the player's copy actions can access the clipboard.

On the inline embed

Options are attributes on the div:

<div data-sfd-video="<video-id>"
     data-base="https://<library>.video.simplifyd.app"
     data-start="90"
     data-autoplay="muted"></div>
<script src="https://<library>.video.simplifyd.app/player/<version>/player.js" defer></script>
AttributeWhat it does
data-startWhere to start, in the same forms t takes.
data-autoplay="muted"Start playback muted. Use the value muted.
data-titleAn accessible name for the player, for a page where the surrounding text does not already give one.
data-datafree="false"Leave out the lines that tell the viewer watching costs them no data.
data-beacon=""Turn the playback beacon off for this embed. Nothing is then measured for these views, and they do not appear in edge video stats.

Use the data-base value and script URL from the generated snippet. Do not construct or modify these URLs manually.


What a viewer can do

Right-click the player to open its video controls. Right-click again inside that menu to open the browser's context menu.

ItemWhat it does
Play, PauseThe same as clicking the picture.
LoopStart again at the end. Stays ticked until it is turned off.
Playback speedHalf speed up to double.
Picture in picturePop the video out of the page, where the browser allows it.
Copy video linkThe video's own address.
Copy link at current timeA video link that starts at the current playback time.
Copy embed codeThe frame snippet, the same one edge video embed prints.
Playback statsData usage, current resolution and bitrate, buffer, and dropped frames. Byte counts are unavailable in browsers that do not report them, including native playback on Safari and iPhone.

The keyboard works throughout, so the player is usable without a pointer:

KeyWhat it does
Space or KPlay or pause
← →Back or forward five seconds
↑ ↓Volume
MMute
FFull screen
Menu or Shift+F10Open the right-click menu; arrows move through it, Esc closes it

stats

Show how a video is being watched. Leave off the id to see the whole library.

edge video stats <video-id> -l training
edge video stats -l training --from 2026-08-01 --to 2026-08-31

Flags

FlagDescription
--fromStart date, YYYY-MM-DD. Defaults to four weeks ago.
--toEnd date, YYYY-MM-DD. Defaults to today.

Statistics include views, watch time, completion, and data-free playback broken down by mobile network.


captions

Attach a WebVTT caption file, or remove one.

edge video captions add <video-id> captions.en.vtt -l training
edge video captions rm <video-id> <track-id> -l training

The language is taken from the file name when it is written like captions.en.vtt. Set it yourself with --language, and set the name viewers see with --label.


poster

Choose the still frame shown before someone presses play.

edge video poster <video-id> 1:07 -l training

Specify the time in seconds (42), mm:ss, or hh:mm:ss. The poster updates after a few seconds. Playback and video links are unaffected.

This needs the library to be keeping original files.


reprocess

Prepare a video again from the original file you uploaded.

edge video reprocess <video-id> -l training --wait

Use this after changing a library setting that you want applied to a video already uploaded. It does the work again, so it is charged again.


rm

Delete a video and everything made from it.

edge video rm <video-id> -l training
edge video rm <video-id> -l training --yes

Deletion is permanent. Pass --yes to skip confirmation in scripts.


Example script: upload a folder of videos

Use this script to upload a folder of videos to a library.

The script uploads each file, skips anything already there, and picks up where it left off if the connection drops.

#!/usr/bin/env bash
# Upload every video in a folder to a library.
# Needs: edge, jq
# Run `edge auth login` and `edge link` first, so the workspace,
# project and environment are already set.

set -uo pipefail

LIBRARY="training"
FOLDER="./videos"
ATTEMPTS=5          # tries per file before moving on

# What is already in the library, so a rerun does not upload twice.
existing=$(edge video list --library "$LIBRARY" --limit 1000 --json \
  | jq -r '(.videos // [])[].title')

shopt -s nullglob
for file in "$FOLDER"/*.mp4 "$FOLDER"/*.mov; do
  title=$(basename "${file%.*}")

  if grep -Fxq "$title" <<< "$existing"; then
    echo "-- $title is already there, skipping"
    continue
  fi

  echo "==> $title"
  resume=""

  for attempt in $(seq 1 "$ATTEMPTS"); do
    if [ -n "$resume" ]; then
      out=$(edge video upload "$file" --library "$LIBRARY" --resume "$resume" 2>&1)
    else
      out=$(edge video upload "$file" --library "$LIBRARY" 2>&1)
    fi
    code=$?
    echo "$out"

    if [ $code -eq 0 ]; then
      echo "   done"
      break
    fi

    # When an upload is cut short the CLI prints the command to carry on.
    # Take the id out of it and send only what is missing.
    resume=$(grep -o -- '--resume [^ ]*' <<< "$out" | tail -1 | cut -d' ' -f2)

    if [ -z "$resume" ]; then
      echo "   could not be uploaded, moving on"
      break
    fi
    echo "   interrupted, trying again ($attempt of $ATTEMPTS)"
    sleep 5
  done
done

The script waits for uploads to complete. Video processing continues after the script exits.

Checking on them afterwards

# Anything still being worked on
edge video list -l training --limit 1000

# Anything that did not work, with the reason
edge video list -l training --limit 1000 --json \
  | jq -r '(.videos // [])[] | select(.status == "failed") | "\(.title): \(.status_message)"'

Errors

MessageCauseFix
a video library is required: pass --library <name>No library namedAdd --library <name>
"..." is not a video container we can readThe file is not a type we acceptUse .mp4, .mov, .mkv, .webm or .m4v
that file is ...; the limit for a single upload is 8.0 GiBThe file is too largeSplit it, or ask us to raise the limit
video ... was registered for a N-byte file and ... is M bytes--resume was given a different fileResume with the same file, or upload this one on its own
this video is processing; an embed exists once it is readyThe video is not finished yetWait, then run edge video embed again
not authenticatedNo token foundRun edge auth login, or set CLOUD_TOKEN