Documentation

Build with Cloudmotion

Upload Remotion bundles and start renders on managed Lambda. Use the SDK or CLI for day-to-day work, or call the HTTP API directly when you need full control. All paths below are under https://api.cloudmotion.dev.

Get started

Node.js SDK

Install the cloudmotion package from npm. It wraps upload and render in a small client — pass your API key to createClient() (the SDK does not read env vars unless you pass them in). (npm)

Install

npm install cloudmotion

Upload and render

import { createClient } from 'cloudmotion';

const client = createClient({ token: process.env.CLOUDMOTION_TOKEN });

const bundle = await client.uploadBundle({
  bundleDir: 'dist/bundle',
  bundleId: 'my-project',
});

const { renderId } = await client.renderMedia({
  bundleId: bundle.bundleId,
  compositionId: 'Main',
  inputProps: { title: 'Hello' },
});

let status = await client.getRenderProgress(renderId);
while (status.status !== 'completed' && status.status !== 'failed') {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  status = await client.getRenderProgress(renderId);
}

if (status.status === 'failed') {
  throw new Error(status.error ?? 'Render failed');
}

console.log(status.outputUrl);

CLI

The cloudmotion binary ships in the same npm package. Export CLOUDMOTION_TOKEN (your cm_… key) before running commands.

Run

npx cloudmotion

Upload and render

export CLOUDMOTION_TOKEN=cm_...

npx cloudmotion bundles upload dist/bundle --bundle-id my-project

npx cloudmotion render \
  --bundle-id my-project \
  --composition-id Main

Workflow

  1. Bundle your Remotion site (npx remotion bundle), then upload: POST /v1/bundles/upload/init → PUT each file to its presigned URL → POST /v1/bundles/upload/complete.
  2. Start a render with POST /v1/renders (video or still).
  3. Poll GET /v1/renders/:renderId until the job is completed or failed.

Authentication

Send your project API key (cm_…) from the dashboard on every request.

Authorization: Bearer cm_…

Identifiers

bundleId
Stable slug for a Remotion project. Uploading again under the same ID replaces the latest bundle used for renders. Pattern: /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/
uploadId
Opaque id from POST /v1/bundles/upload/init. Pass it back unchanged on upload/complete. Not used for renders. Pattern: /^[a-z0-9]{20}$/

Bundle directory

After npx remotion bundle, upload the full output folder. Paths are relative to that directory root — do not wrap files in an extra parent folder.

dist/bundle/
├── index.html
├── bundle.js
├── favicon.ico
└── source-map-helper.wasm

Real bundles often include extra files (chunks, source maps, assets). List every file in upload/init — the tree below is a minimal example.

HTTP API reference

Version /v1/ — same operations the SDK and CLI call under the hood.

POST /v1/bundles/upload/init

https://api.cloudmotion.dev/v1/bundles/upload/init

Start a bundle upload and get presigned upload URLs for each file.

Request body

{
  "bundleId": "my-project",
  "files": [
    { "path": "index.html", "sizeBytes": 1769 },
    { "path": "bundle.js", "sizeBytes": 901742 },
    { "path": "favicon.ico", "sizeBytes": 104640 },
    { "path": "source-map-helper.wasm", "sizeBytes": 48693 }
  ]
}
Field Required Type Notes
bundleId yes string Must match /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/ (first character must be a letter or digit)
files yes { path, sizeBytes }[] Non-empty. Must include index.html. List every file in the bundle directory. Each path is relative to the bundle root; sizeBytes is the exact byte length of the PUT body (max 10 MiB per file, 100 MiB total, 500 files). Only bundle-safe types are accepted (text/*, application/javascript, application/json, application/wasm, fonts, images, audio, and video).

Response

{
  "bundleId": "my-project",
  "uploadId": "k7f3a9m2p1w4n8x6q0z1",
  "expiresAt": "2026-05-24T13:00:00.000Z",
  "uploads": [
    {
      "path": "bundle.js",
      "method": "PUT",
      "url": "https://…s3…?X-Amz-…",
      "headers": {
        "Content-Type": "text/javascript",
        "Content-Length": "901742"
      }
    },
    {
      "path": "favicon.ico",
      "method": "PUT",
      "url": "https://…s3…?X-Amz-…",
      "headers": {
        "Content-Type": "image/x-icon",
        "Content-Length": "104640"
      }
    },
    {
      "path": "index.html",
      "method": "PUT",
      "url": "https://…s3…?X-Amz-…",
      "headers": {
        "Content-Type": "text/html",
        "Content-Length": "1769"
      }
    }
  ]
}
  • uploadId — Opaque id from this init call; required on upload/complete
  • expiresAt — ISO-8601 time when presigned URLs expire
  • uploads — One entry per manifest file (sorted by path). PUT each url with exactly the headers object (Content-Type is inferred from the file extension; only bundle-safe types are allowed).
  • URLs expire after a short window (see presigned query params).
  • Each project may have at most 20 distinct bundle IDs. Re-uploading an existing bundle ID does not count toward the limit.

POST /v1/bundles/upload/complete

https://api.cloudmotion.dev/v1/bundles/upload/complete

Finish the bundle upload after all files are PUT to their presigned URLs.

Request body

{
  "bundleId": "my-project",
  "uploadId": "k7f3a9m2p1w4n8x6q0z1"
}
Field Required Type Notes
bundleId yes string
uploadId yes string Value from upload/init

Response

{ "ok": true, "remotionVersion": "4.0.236", "runtimeReady": true }
  • remotionVersion — Remotion version detected from the uploaded bundle
  • runtimeReady — When false, the bundle is not ready for renders yet; wait and retry POST /v1/renders (may return 409 until ready).

POST /v1/renders

https://api.cloudmotion.dev/v1/renders

Start a video or still render.

Request body

{
  "bundleId": "my-project",
  "compositionId": "Main",
  "kind": "media",
  "inputProps": { "title": "Hello" },
  "codec": "h264",
  "width": 1920,
  "height": 1080
}
Field Required Type Notes
bundleId yes string
compositionId yes string
kind no "media" | "still" Defaults to media when omitted or any value other than still
inputProps no object JSON passed to the composition
codec no string Media renders only
imageFormat no string Still renders only (e.g. png, jpeg)
width no number
height no number
frame no number Still renders — frame index to capture

Response

{ "renderId": "abc123" }
  • Still example: set "kind": "still" and include "frame".
  • Returns 409 when the bundle is not ready yet (see runtimeReady on upload complete).

GET /v1/renders/:renderId

https://api.cloudmotion.dev/v1/renders/{renderId}

Fetch render status and progress.

Response

{
  "status": "rendering",
  "progress": 42,
  "outputUrl": "https://…"
}
  • status — queued | rendering | completed | failed
  • progress — 0–100 percent
  • outputUrl — Present when completed
  • error — Present when failed

Errors

Errors return JSON { "message": "…" } with a matching HTTP status.

  • 400 Invalid JSON, missing fields, or validation failure
  • 401 Missing or invalid API key / session
  • 404 Render not found (GET /v1/renders/:id)
  • 500 Unexpected server or upstream failure