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 underhttps://api.cloudmotion.dev.

Get started

Node.js SDK

Install cloudmotion from npm. Pass your API key to createClient() (the SDK does not read env vars). Use cloudmotion for render/poll; import uploadBundle from cloudmotion/node for bundle uploads. (npm)

Install

npm install cloudmotion

Upload and render

import { createClient } from 'cloudmotion';
import { uploadBundle } from 'cloudmotion/node';

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

const bundle = await uploadBundle(client, {
  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 inupload/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 }
  ]
}
FieldRequiredTypeNotes
bundleIdyesstringMust match /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/ (first character must be a letter or digit)
filesyes{ 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"
}
FieldRequiredTypeNotes
bundleIdyesstring
uploadIdyesstringValue 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,
  "concurrency": 50
}
FieldRequiredTypeNotes
bundleIdyesstring
compositionIdyesstring
kindno"media" | "still"Defaults to media when omitted or any value other than still
inputPropsnoobjectJSON passed to the composition
codecno"h264" | "h265" | "vp8" | "vp9" | "gif" | "prores" | "mp3" | "aac" | "wav"Media renders only; defaults to h264 when omitted
imageFormatno"png" | "jpeg" | "webp"Still renders only; defaults to png when omitted
widthnonumber
heightnonumber
concurrencynonumberMedia renders only — number of Lambda functions (integer 1–200). Mutually exclusive with framesPerLambda. Omit for Remotion default.
framesPerLambdanonumberMedia renders only — frames per Lambda invocation (positive integer). Mutually exclusive with concurrency. Omit for Remotion default.
framenonumberStill 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 — short-lived signed download URL (≈1 hour). Not for public hosting; download or copy to your own storage, then serve from there. If it expires, call this endpoint again for a fresh URL.
  • error — Present when failed

Errors

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

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