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
}
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—
concurrencynonumberNot available on the trial. Sending this field returns 400. Remotion picks concurrency.
framesPerLambdanonumberNot available on the trial. Sending this field returns 400. Remotion picks frames per Lambda.
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), or when another render is already in progress.
  • Returns 402 when render credits are exhausted.

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