> ## Documentation Index
> Fetch the complete documentation index at: https://heygen-1fa696a7.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Hyperframes Cloud Rendering

> Render programmatic, code-driven video with the HeyGen Hyperframes API. Upload an HTML project bundle, inject runtime variables, and HeyGen renders it to MP4, WebM, or MOV.

<img className="w-full h-44 object-cover rounded-xl" src="https://mintcdn.com/heygen-1fa696a7/hfMXXwJzjE7vBSYZ/images/theme/research-5.webp?fit=max&auto=format&n=hfMXXwJzjE7vBSYZ&q=85&s=422da140ece4b799d490f85bb2013ca5" alt="" noZoom width="1400" height="788" data-path="images/theme/research-5.webp" />

Hyperframes turns a self-contained HTML/CSS/JS project into a rendered video. You package your composition as a `.zip`, upload it, and HeyGen runs it through a headless renderer — ideal for motion graphics, data-driven visuals, and templated pipelines. Renders are billed per minute of output — rates are on the [self-serve](/docs/pricing#hyperframes) and [enterprise](/docs/enterprise-pricing#hyperframes) pricing pages. For worked examples, see the [Hyperframes cookbook](/motion-graphics).

If you work from a terminal, [`hyperframes cloud render`](https://hyperframes.heygen.com/deploy/cloud) in the Hyperframes CLI wraps this entire flow — it zips your project, uploads it, submits the render, polls, and downloads the finished video in one command. This page documents the underlying API so you can build the same pipeline into your own application.

## Prerequisites

<Check>
  A project `.zip` containing an `index.html` at the root (or at the path you set in `composition`), plus any CSS, JS, fonts, and assets it needs. The bundle must render standalone in a browser.
</Check>

<Check>
  The zip available as a `url`, an uploaded `asset_id`, or `base64`. Use [Assets](/assets) to upload a file and get an `asset_id`.
</Check>

When zipping, include only what the composition needs — leave out `.git`, `node_modules`, and build output like `dist`. A lean bundle uploads faster, and `asset_id` uploads accept larger projects than the `url` and `base64` caps (see [`hyperframes_project_too_large`](/docs/error-codes#hyperframes-project-too-large)).

## How a render flows

A render is three API calls end to end — upload the bundle, submit the render, then poll (or [take a webhook](#using-webhooks-instead-of-polling)) until the video is ready:

```
 Your app                              HeyGen cloud
┌─────────────────────────┐          ┌─────────────────────────────────┐
│ zip the project         │ ──POST──▶│ /v3/assets                      │
│                         │  upload  │  → asset_id                     │
│                         │          │                                 │
│                         │ ──POST──▶│ /v3/hyperframes/renders         │
│                         │  submit  │  → render_id (queued)           │
│                         │          │  Chromium + FFmpeg render       │
│ poll GET /renders/{id}  │ ◀────────│  queued → rendering → completed │
│ download the video      │ ◀────────│  presigned video_url            │
└─────────────────────────┘          └─────────────────────────────────┘
```

1. **Upload** the project zip to `POST /v3/assets` and note the `asset_id`. (For small or already-hosted bundles, skip this step and pass the project as `url` or `base64` instead.)
2. **Submit** the render to `POST /v3/hyperframes/renders`, which returns a `render_id` immediately.
3. **Poll** `GET /v3/hyperframes/renders/{render_id}` until the status is `completed`, then download the video from the presigned `video_url`.

The sections below walk through each call.

## Step 1 — Build your composition

Author an HTML page that renders your scene. Hyperframes compositions are standard, self-contained web pages — for the composition framework, component APIs, and local preview and render tooling, see the [Hyperframes developer docs](https://hyperframes.heygen.com/packages/core). To explore, remix, and learn from community-built compositions, browse the [Community Playground](https://www.hyperframes.dev).

To make a render data-driven, read values from `data-composition-variables` on the document — HeyGen overrides these with the `variables` object you send at render time, so one bundle can produce many videos (see [Upload once, render many](#upload-once-render-many) below). The renderer validates the bundle when the render starts; a zip without a usable entry file fails with [`hyperframes_project_invalid`](/docs/error-codes#hyperframes-project-invalid), so preview the project locally (or run it through the Hyperframes CLI) before submitting.

## Step 2 — Create a render

`POST /v3/hyperframes/renders` with your project bundle. The call returns `202 Accepted` with a `render_id`:

```bash theme={null}
curl -X POST "https://api.heygen.com/v3/hyperframes/renders" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project": { "type": "asset_id", "asset_id": "YOUR_PROJECT_ZIP_ASSET_ID" },
    "fps": 30,
    "quality": "standard",
    "format": "mp4",
    "resolution": "1080p",
    "aspect_ratio": "16:9",
    "composition": "index.html",
    "variables": { "headline": "Q2 Revenue", "value": "$1.2M" },
    "title": "Q2 revenue motion graphic"
  }'
```

```json theme={null}
{
  "data": {
    "render_id": "hfr_abc123"
  }
}
```

## Step 3 — Poll for completion

Poll `GET /v3/hyperframes/renders/{render_id}` until `status` is `completed`:

```bash theme={null}
curl -X GET "https://api.heygen.com/v3/hyperframes/renders/YOUR_RENDER_ID" \
  -H "x-api-key: YOUR_API_KEY"
```

| Status      | Meaning                                               |
| ----------- | ----------------------------------------------------- |
| `queued`    | Accepted and waiting for a renderer                   |
| `rendering` | Render in progress                                    |
| `completed` | Ready — `video_url` and `thumbnail_url` are available |
| `failed`    | Something went wrong — check `failure_message`        |

A completed render returns the full `HyperframesRenderDetail`, including `video_url`, `thumbnail_url`, `duration`, and the settings the render used.

<Note>
  `video_url` and `thumbnail_url` are short-lived presigned URLs. Download the video promptly, and when you need a fresh link later, re-fetch the render with `GET /v3/hyperframes/renders/{render_id}` rather than storing the URL.
</Note>

## Upload once, render many

The idiomatic template workflow: upload the project zip to [Assets](/assets) once, then submit as many renders as you need against the same `asset_id`, each with different `variables` — no re-zip, no re-upload:

```bash theme={null}
curl -X POST "https://api.heygen.com/v3/hyperframes/renders" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project": { "type": "asset_id", "asset_id": "YOUR_PROJECT_ZIP_ASSET_ID" },
    "variables": { "name": "Ada", "theme": "dark" }
  }'

curl -X POST "https://api.heygen.com/v3/hyperframes/renders" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project": { "type": "asset_id", "asset_id": "YOUR_PROJECT_ZIP_ASSET_ID" },
    "variables": { "name": "Linus", "theme": "light" }
  }'
```

Declare the variables (and their types) in the composition's `data-composition-variables`; the renderer validates the `variables` you send against that schema when the render runs.

## Managing renders

List your renders (paginated) with `GET /v3/hyperframes/renders`, or remove one with `DELETE /v3/hyperframes/renders/{render_id}`:

```bash theme={null}
curl -X GET "https://api.heygen.com/v3/hyperframes/renders" \
  -H "x-api-key: YOUR_API_KEY"

curl -X DELETE "https://api.heygen.com/v3/hyperframes/renders/YOUR_RENDER_ID" \
  -H "x-api-key: YOUR_API_KEY"
```

## Full example

```python theme={null}
import requests
import time

API_KEY = "YOUR_API_KEY"
BASE = "https://api.heygen.com"
HEADERS = {"x-api-key": API_KEY, "Content-Type": "application/json"}

# 1. Submit the render
resp = requests.post(f"{BASE}/v3/hyperframes/renders", headers=HEADERS, json={
    "project": {"type": "asset_id", "asset_id": "YOUR_PROJECT_ZIP_ASSET_ID"},
    "fps": 30,
    "quality": "standard",
    "format": "mp4",
    "resolution": "1080p",
    "aspect_ratio": "16:9",
    "variables": {"headline": "Q2 Revenue", "value": "$1.2M"},
})
render_id = resp.json()["data"]["render_id"]
print(f"Render queued: {render_id}")

# 2. Poll until done
while True:
    status_resp = requests.get(f"{BASE}/v3/hyperframes/renders/{render_id}", headers=HEADERS)
    data = status_resp.json()["data"]
    print(f"Status: {data['status']}")
    if data["status"] == "completed":
        print(f"Download: {data['video_url']}")
        break
    elif data["status"] == "failed":
        print(f"Error: {data.get('failure_message')}")
        break
    time.sleep(10)
```

## Parameters

| Parameter      | Type    | Required | Description                                                                                         |
| -------------- | ------- | -------- | --------------------------------------------------------------------------------------------------- |
| `project`      | object  | Yes      | The project `.zip` as a `url`, `asset_id`, or `base64` input                                        |
| `fps`          | integer | No       | Frames per second, 1–240. Default `30`                                                              |
| `quality`      | string  | No       | `draft`, `standard` (default), or `high`                                                            |
| `format`       | string  | No       | `mp4` (default), `webm`, or `mov`. `webm` and `mov` carry an alpha channel for transparent overlays |
| `resolution`   | string  | No       | `1080p` (default) or `4k`. 4k is billed at 1.5× — see [pricing](/docs/pricing#hyperframes)          |
| `aspect_ratio` | string  | No       | `16:9` (default), `9:16`, or `1:1`                                                                  |
| `composition`  | string  | No       | Entry HTML path inside the zip. Default `index.html`                                                |
| `variables`    | object  | No       | Key–value overrides for `data-composition-variables`                                                |
| `title`        | string  | No       | Display name, ≤500 characters                                                                       |
| `callback_id`  | string  | No       | Your own correlation ID, ≤256 characters, echoed back in the webhook                                |
| `callback_url` | string  | No       | Webhook URL — receive a POST when the render finishes                                               |

<Note>
  Render `4k` as `mp4`; the 4k supersampling pipeline produces opaque frames. For transparent output, render `webm` or `mov` at `1080p`.
</Note>

## Safe retries

Both `POST /v3/assets` and `POST /v3/hyperframes/renders` accept an `Idempotency-Key` header, so a network failure or timeout never leaves you guessing whether the upload or render went through — retry with the same key and the API replays the original response instead of creating (and billing) a duplicate:

```bash theme={null}
curl -X POST "https://api.heygen.com/v3/hyperframes/renders" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{ "project": { "type": "asset_id", "asset_id": "YOUR_PROJECT_ZIP_ASSET_ID" } }'
```

Keys are 1–255 characters from `[A-Za-z0-9_:.-]` — a UUID is a safe default. Replays work for 24 hours, and a retry that arrives while the original is still processing gets a `409` [`request_in_progress`](/docs/error-codes#request-in-progress). Idempotency is scoped per endpoint, so reusing one key for an upload and its follow-up render is safe.

## Using webhooks instead of polling

Pass a `callback_url` (and optionally a `callback_id` to correlate the response) when you submit the render. HeyGen posts to it whether or not anything is polling, so you can submit the render and walk away — true fire-and-forget. Register an endpoint via `POST /v3/webhooks/endpoints` and subscribe to the `hyperframes_video.success` and `hyperframes_video.fail` events described in [Webhook Events](/docs/webhook-events).
