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
- 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. - Start a render with
POST /v1/renders(video or still). - Poll
GET /v1/renders/:renderIduntil the job iscompletedorfailed.
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 onupload/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