CaptureBeamDemo compiler
API reference

Render API

POST a YAML, poll for the result, get an MP4 URL. The same flow the dashboard uses internally — and the same code path the local CLI runs.

Base URL

https://your-domain.com/api/v1

For local dev: http://localhost:3000/api/v1.

Authentication

Every request needs an API key. Create one in your dashboard; it's shown once, stored hashed. Send it as a Bearer token:

Authorization: Bearer cb_live_xxxxxxxxxxxxx

The header form x-api-key: cb_live_… is also accepted for tools that don't play nicely with Authorization.

Endpoints

POST/v1/renders

Submit a demo for rendering. Returns immediately with a job ID.

Three body shapes are accepted:

  • application/json{ "yaml": "title: ..." }
  • application/json{ "projectId": "pj_..." }
  • text/yaml — raw YAML body

Example

curl -X POST https://your-domain.com/api/v1/renders \
  -H "Authorization: Bearer cb_live_xxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"projectId": "pj_a1b2c3d4"}'

# 202 Accepted
{
  "id": "rj_a1b2c3d4e5f6",
  "status": "pending"
}
GET/v1/renders/{id}

Poll a job's status. Poll every 2-3s; jobs typically take 20-40s.

Example

curl https://your-domain.com/api/v1/renders/rj_a1b2c3d4e5f6 \
  -H "Authorization: Bearer cb_live_xxxxxxxxxxxxx"

# 200 OK — pending or running
{ "id": "rj_…", "status": "running",
  "videoUrl": null, "manifestUrl": null,
  "steps": null, "error": null,
  "durationMs": null, "createdAt": "2026-01-15T12:00:00Z" }

# 200 OK — done
{ "id": "rj_…", "status": "succeeded",
  "videoUrl": "https://media.your-domain.com/render-…-mp4",
  "manifestUrl": "https://media.your-domain.com/manifest-…-json",
  "durationMs": 22400,
  "steps": [
    { "index": 0, "type": "goto", "status": "ok", "durationMs": 1240 },
    { "index": 1, "type": "click", "status": "ok",
      "resolvedSelector": "getByRole(button, name=\"Sign in\")",
      "durationMs": 380 }
  ],
  "createdAt": "2026-01-15T12:00:00Z" }

# 200 OK — succeeded with a partial step failure
{ "id": "rj_…", "status": "succeeded",
  "videoUrl": "https://…",
  "steps": [
    { "index": 0, "type": "goto", "status": "ok" },
    { "index": 1, "type": "click", "status": "failed",
      "error": "Could not resolve target { role: \"button\", name: \"Buy\" } …" }
  ], … }

# 200 OK — failed (CLI-level, no manifest)
{ "id": "rj_…", "status": "failed",
  "error": "CLI exited with code 1: …",
  "steps": null }

videoUrl and manifestUrl are presigned URLs valid for S3_URL_TTL seconds (24h default). steps is the compact per-step summary — index, type, ok/skipped/failed, optional error, optional resolved selector, optional recovery path. The full events.json (cursor paths, anchor screenshots, etc.) lives at manifestUrl.

Status codes

CodeMeaning
202Render queued. Poll the job ID.
200Job state retrieved.
400Missing or malformed body. Check yaml / projectId.
401Missing or invalid API key.
402No active subscription. Upgrade.
404Project not found, or doesn't belong to the API key's account.
429Concurrency limit (3 in flight) or yearly safety net (5,000) hit.
500Server error. The job row stays in pending; retry safe.

Common patterns

Render-and-wait helper

Most CI integrations want a single blocking call. This shell pattern submits and polls until done:

#!/usr/bin/env bash
set -euo pipefail
KEY=${CAPTUREBEAM_KEY:?}

JOB_ID=$(curl -sS -X POST https://your-domain.com/api/v1/renders \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"projectId":"pj_a1b2c3d4"}' | jq -r .id)

while :; do
  RES=$(curl -sS https://your-domain.com/api/v1/renders/$JOB_ID \
    -H "Authorization: Bearer $KEY")
  STATUS=$(echo "$RES" | jq -r .status)
  case "$STATUS" in
    succeeded) echo "$RES" | jq -r .videoUrl; exit 0 ;;
    failed)    echo "$RES" | jq -r .error >&2; exit 1 ;;
    *)         sleep 3 ;;
  esac
done

Rate limits & soft caps

  • Concurrency: max 3 renders in flight per account. The 4th returns 429.
  • Yearly safety net: 5,000 successful renders per rolling 12 months. Soft anti-abuse cap, not advertised. Email support to lift.
  • No request-rate limit on the polling endpoints.

Webhooks

Webhooks for render completion are on the roadmap. For now, poll the status endpoint. The job row is durable — even if your client reconnects, you can resume polling with the original ID.