SPVD Render API Reference

Fill these in to personalise every example on this page. Nothing is sent anywhere.

Render API reference

Endpoints, request bodies and event payloads of the Render API. For concepts and workflows see the Render guide.

Base URLhttps://render-host/api/v1/render
FormatJSON request and response bodies (Content-Type: application/json).
AuthExecution URLs and status streams: none. POST endpoints: Authorization: Bearer <render key>.
TimesISO-8601 UTC strings. Durations (ttl, timeout) are in milliseconds, sizes in bytes.

Endpoints

Signed execution URLno auth

GET{executionUrl}

Start (or re-attach to) the job described by a signed execution URL. Use the URL exactly as received; it already contains the job and its limits.

Response: StartResponse202 new job, 200 existing job.

const res = await fetch(urls.executionUrl);
const start = await res.json();   // 202 = new job, 200 = already exists
console.log(start.jobId, start.status);

Start a jobBearer auth

POST/api/v1/render/execute

Validate and immediately start a job.

Body: JobRequest. Response: StartResponse202 new, 200 already exists, 400 invalid body, 401 bad token.

const res = await fetch("https://api-host/api/v1/render/execute", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_RENDER_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    id: "clip-8f14e45f",
    inputs: [{ url: "https://cdn.example.com/clip.mp4", ext: "mp4" }],
    output: { ext: "m4a" },
    operation: { type: "extract_audio" },
  }),
});
const job = await res.json();
console.log(job);

Cache a jobBearer auth

POST/api/v1/render/cache

Store a job description without running it and get a public execution URL. Re-caching the same id replaces the stored description.

Body: JobRequest. Response: CacheResponse (200).

const res = await fetch("https://api-host/api/v1/render/cache", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_RENDER_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    id: "clip-8f14e45f",
    inputs: [{ url: "https://cdn.example.com/clip.mp4", ext: "mp4" }],
    output: { ext: "m4a" },
    operation: { type: "extract_audio" },
  }),
});
const cached = await res.json();   // { cacheId, executionUrl, statusUrl, expiresAt }
console.log(cached);

Run a cached jobno auth

GET/api/v1/render/execute/{cacheId}

Start the job stored by Cache a job. Usually called via the returned executionUrl.

ParameterInDescription
cacheIdpathThe cacheId returned when caching.

Response: StartResponse, or 404 {"error": "Cache with ID '…' not found."}.

const res = await fetch(cached.executionUrl);   // or `https://api-host/api/v1/render/execute/${cacheId}`
if (res.status === 404) throw new Error("Cached job not found");
const start = await res.json();
console.log(start.jobId, start.status);

Status stream (WebSocket)no auth

WS/api/v1/render/status/{jobId}

Upgrade to a WebSocket and receive a StatusEvent text frame about every second. The server closes the socket after done, failed or not_found. Nothing needs to be sent by the client.

const ws = new WebSocket(job.statusUrl);

ws.onmessage = (event) => {
  const job = JSON.parse(event.data);
  console.log(job.status, job.progress);

  if (job.status === "done") console.log("Download:", job.output.url);
  if (job.status === "failed" || job.status === "not_found") console.error(job.error);
};

Status stream (Server-Sent Events)no auth

SSE/api/v1/render/status/sse/{jobId}

GET with Accept: text/event-stream. Each event's data field is a JSON StatusEvent. The stream ends after a terminal status.

function watchRender(sseUrl) {
  return new Promise((resolve, reject) => {
    const events = new EventSource(sseUrl);
    events.onmessage = (event) => {
      const job = JSON.parse(event.data);
      console.log(job.status, job.progress);

      if (job.status === "done") { events.close(); resolve(job.output); }
      if (job.status === "failed" || job.status === "not_found") { events.close(); reject(new Error(job.error)); }
    };
  });
}

const output = await watchRender(job.sseStatusUrl);
console.log("Download:", output.url);

Healthno auth

GET/

Returns plain text OK when the service is running.

const ok = (await (await fetch("https://api-host/")).text()) === "OK";
console.log(ok);

Request objects

JobRequest

FieldTypeRulesDescription
id requiredstringmin 5 charsYour unique job id. Same id = same job (see idempotency). Use a UUID or a hash of the inputs.
inputs requiredInput[]1–10 itemsSource files, in the order the operation expects.
output requiredOutputOptionsWhat to produce.
operation requiredOperationWhat to do with the inputs.
ttlinteger (ms)180 000 – 86 400 000How long the job and file are kept. Default 3 600 000 (1 hour).
progressBracketsProgressBracketsHow overall progress is split between stages.
limitsLimitseach limit ≤ 5 GBSize guards for inputs. Defaults to 5 GB per input and 5 GB in total.
{
  "id": "yt-dQw4w9WgXcQ-1080p",
  "inputs": [
    {
      "url": "https://cdn.example.com/video-1080p.mp4",
      "ext": "mp4",
      "timeout": 240000,
      "chunkDownload": { "type": "header", "size": 33554432, "concurrency": 4 }
    },
    { "url": "https://cdn.example.com/audio.m4a", "ext": "m4a" }
  ],
  "output": { "ext": "mp4", "downloadName": "Never Gonna Give You Up (1080p).mp4" },
  "operation": { "type": "join_streams", "copyStreams": true },
  "ttl": 7200000,
  "progressBrackets": { "downloading": [0, 50], "processing": [50, 60], "uploading": [60, 100] },
  "limits": { "inputSize": { "maxSizePerInput": 1572864000, "maxTotalSize": 1677721600 } }
}

Input

FieldTypeRules / defaultDescription
url requiredstringabsolute URLWhere to download the file from.
ext requiredstringInput extension, e.g. mp4, m4a, webm, m3u8. See extensions.
requestOptions.methodstringGETHTTP method.
requestOptions.headersobject<string,string>Extra request headers (referer, cookie, user-agent…).
requestOptions.bodystringRequest body.
chunkDownload.typeheader | queryheaderHow byte ranges are requested: Range header or a range query parameter.
chunkDownload.sizeinteger (bytes)≥ 1, default 10 485 760 (10 MB)Chunk size.
chunkDownload.concurrencyinteger1–10, default 2Parallel chunks.
HLSPlaylistDownload.concurrencyinteger1–10, default 2Parallel segment downloads for m3u8 inputs.
timeoutinteger (ms)1 000 – 600 000, default 120 000Maximum time to download this input.

OutputOptions

FieldTypeRules / defaultDescription
ext requiredstringOutput extension, e.g. mp4, m4a, mp3, webm.
downloadNamestringtrimmed, newlines removedFile name suggested to browsers when downloading output.url.
chunkUpload.sizeinteger (bytes)≥ 1, default 104 857 600 (100 MB)Upload part size.
chunkUpload.concurrencyinteger1–10, default 4Parallel uploads.

Operation

FieldTypeDefaultDescription
type requiredOperationTypeThe operation to run.
copyStreamsbooleantrueCopy streams without re-encoding (fast). false re-encodes.

ProgressBrackets

Three [start, end] ranges that must be continuous and cover 0–100: downloading[0] = 0, downloading[1] = processing[0], processing[1] = uploading[0], uploading[1] = 100.

{ "downloading": [0, 70], "processing": [70, 85], "uploading": [85, 100] }

Limits

FieldTypeDescription
inputSize.maxSizePerInputinteger (bytes)Fail if any single input is larger. Maximum and default: 5 368 709 120 (5 GB).
inputSize.maxTotalSizeinteger (bytes)Fail if all inputs together are larger. Maximum and default: 5 368 709 120 (5 GB).

Response objects

StartResponse

FieldTypeDescription
jobIdstringThe job id (your id).
statusqueued | already_existsqueued for a new job, already_exists when attaching to an existing one.
messagestringHuman-readable summary.
statusUrlstring (wss)WebSocket status stream.
sseStatusUrlstring (https)Server-Sent Events status stream.

CacheResponse

FieldTypeDescription
cacheIdstringEquals the job id.
executionUrlstring (https)Public URL that starts the job with GET.
statusUrlstring (wss)WebSocket status stream for the job.
expiresAtstring (date-time)When the job would expire if started now (now + ttl).

StatusEvent

FieldTypeDescription
job_idstringJob id.
statusJobStatusCurrent stage.
progressintegerOverall progress 0–100.
outputJobOutput | nullSet when status is done.
errorstring | nullSet when status is failed.
created_atstring (date-time)When the job was first created.
expires_atstring (date-time)When the job and its file will be deleted.

When no job exists for the id, a single event with a different shape is sent and the stream closes:

{ "id": "JOB_ID", "status": "not_found", "error": "Job with ID 'JOB_ID' not found." }

JobOutput

FieldTypeDescription
urlstring (https)Download link, valid until expires_at.
sizeintegerFile size in bytes.
sizeTextstringReadable size, e.g. 93.68 MB.
keystringStorage key of the file. Informational; don't rely on its format.

Error bodies

WhenBody
Body validation failed (400){ "success": false, "error": { … } } describing each invalid field.
Cached job missing (404){ "error": "Cache with ID '…' not found." }
Bad Bearer token (401)Plain text Unauthorized.
Other errors (4xx/5xx){ "message": "…", "code": "…", "statusCode": 500 }

Enums

JobStatus

ValueMeaning
pendingAccepted and about to start.
queuedWaiting for a free worker because the service is busy. It will start automatically; keep listening.
downloading_inputsFetching the source files. progress moves as bytes arrive.
processingMerging / converting.
uploading_outputStoring the result for download.
doneFinished. output is set. Terminal state.
failedStopped with an error; see error. Terminal state.
not_foundNo job with this id exists (never started, or already expired). Only sent by the status streams. Terminal.

OperationType

ValueInputsResult
join_streams1+ (e.g. video, audio)Put all input streams into one file without re-encoding. The usual way to merge a video-only and an audio-only stream.
convert_format1Change the container to output.ext (e.g. HLS/TS → MP4). Re-encodes only when copyStreams is false.
replace_audio_in_video2 — video, then audioKeep the video of input 1 and use the audio of input 2.
extract_audio1Drop the video, keep the audio (e.g. to m4a/mp3).
extract_video1Drop the audio, keep the video.
mix_audio_and_video2 — video with audio, then audioKeep the video of input 1 and mix both audio tracks together (e.g. voice-over on top of the original sound).
no_process1No processing: store the first input as-is and return a download link.

File extensions

Commonly used values for inputs[].ext and output.ext:

KindExtensions
Videomp4 mkv webm mov avi wmv flv mpg mpeg m4v 3gp ogv
Streaming (input only)m3u8
Audiomp3 m4a aac wav ogg flac wma aiff