MP4 Renderer for HyperFrames (Unofficial) avatar

MP4 Renderer for HyperFrames (Unofficial)

Pricing

from $100.00 / 1,000 render completeds

Go to Apify Store
MP4 Renderer for HyperFrames (Unofficial)

MP4 Renderer for HyperFrames (Unofficial)

Render a HyperFrames HTML video composition (ZIP in a key-value store, or inline HTML) to an MP4, up to 1080p and 120 s per run. Unofficial; not affiliated with HeyGen.

Pricing

from $100.00 / 1,000 render completeds

Rating

0.0

(0)

Developer

Salman AlFariz

Salman AlFariz

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

1

Monthly active users

16 days ago

Last modified

Share

Unofficial. Not affiliated with, endorsed by, or sponsored by HeyGen. HyperFrames is a project of HeyGen, Inc.

Turn a HyperFrames HTML video composition into a ready-to-share MP4 in the cloud. Send a ZIP of your project or paste a single HTML file, and get back an H.264 MP4 up to 1080p and 120 seconds long. There's nothing to install: no Chrome, no ffmpeg, no render server to keep running. You pay only for videos that render successfully.

Built for: developers automating video, AI agents that write HTML compositions, marketing and product teams producing personalised clips from one template, and anyone who wants HyperFrames output without running their own render machine.

Tutorial: render a HyperFrames composition to MP4 with one API call. This video was rendered by this actor.

This 36-second tutorial was rendered by this actor (1080p, 60 fps). Watch the full-quality MP4 or see the source composition.

What it does

One HyperFrames composition goes in, one MP4 comes out, per run.

FeatureWhat you get
Two ways to send a compositionA project ZIP stored in one of your Apify key-value stores, or a single HTML file pasted inline
Up to Full HDAny size up to 1920×1080, landscape or portrait (1080×1920 works too)
Up to 120 secondsAt 24, 30 or 60 frames per second
Three quality presetsdraft (fastest), standard (default) or high (slowest, largest file), all at the same price
VariablesPass a JSON object to your composition to render many versions of one template
Same price at any resolution320×180 and 1920×1080 cost the same; you pay for frames, not pixels
Price check before renderingThe actor works out the charge first and stops, uncharged, if it's over your maximum cost per run
No charge for failuresFailed, timed-out, too-expensive, stalled or aborted runs are never charged for rendering
Clear resultsThe MP4, a dataset row with duration, size, frames and charge, and an OUTPUT status record with an error code if something went wrong
Safe by defaultZIPs are checked for unsafe paths and oversized content, and the composition can't reach private networks

How to render your first video

Step 1: Open the actor and keep the example

The Inline HTML field comes prefilled with a 1-second, 320×180 "Hello" composition. Leave it as it is for your first run.

Step 2: Start the run

Click Start. The example renders in about 20 seconds and costs about $0.106 (see Pricing).

Step 3: Download your MP4

Open the run's Output tab and click the MP4 video link, or open Storage → Key-value store and download render.mp4. The dataset row has the same link in videoUrl.

Step 4: Render your own composition

  • Single HTML file with no local assets? Paste it into Inline HTML.
  • Project with images, fonts, video or sub-compositions? Zip it, upload the ZIP to a key-value store (see "Prepare a ZIP project" below), then pick that store and enter the record key. When a ZIP is set, the prefilled HTML is ignored, so you don't need to clear it.

Before longer renders, raise Maximum cost per run in the run options so the price check passes. A 120-second video at 30 fps needs at least $0.71.

Pricing

This actor uses pay-per-event pricing. You pay a fixed price per successful render and per second of video, and nothing for Apify platform usage.

EventPriceWhen it's charged
render-completed$0.10Once, when a render succeeds
frames-30$0.005Per 30 output frames, rounded up, when a render succeeds
apify-actor-start$0.00005 per GB of run memoryWhen any run starts: $0.0008 at the default 16 GB, $0.0004 at 8 GB

30 frames is one second of 30 fps video, so each second at 30 fps costs $0.005, and each second at 60 fps costs $0.01. The number of frames-30 events is the frameUnits field in the output: total frames divided by 30, rounded up. Resolution and quality don't change the price.

Cost examples

VideoFramesframes-30 eventsRender costTotal with start fee (16 GB)
1 s at 320×180, 30 fps (the prefilled example)301$0.105$0.1058
10 s at 30 fps30010$0.15$0.1508
15 s vertical 1080×1920 at 24 fps36012$0.16$0.1608
30 s at 1080p, 30 fps90030$0.25$0.2508
30 s at 1080p, 60 fps1,80060$0.40$0.4008
60 s at 30 fps1,80060$0.40$0.4008
120 s at 1080p, 30 fps3,600120$0.70$0.7008
120 s at 1080p, 60 fps (the most a run can cost)7,200240$1.30$1.3008

What you're never charged for

  • Failed runs. Every error code below ends the run before anything is charged, apart from the start fee.
  • Timed-out, stalled, too-expensive or aborted runs. Same rule: only the start fee.

Renders that complete with warnings (for example a missing font or a blocked asset) are delivered and charged, and each warning is listed in the output.

Maximum cost per run

Before rendering, the actor checks that the expected charge fits your maximum cost per run. If it doesn't, the run stops with EXPECTED_CHARGE_EXCEEDS_MAX and nothing is charged.

  • The smallest allowed maximum is $0.11, which covers a 1-second render.
  • Set it higher for longer videos, using the cost examples above. In the API it's the maxTotalChargeUsd parameter.
  • Add data-duration to your composition's root element so the check happens before any browser starts.

Prices shown are launch prices. The Store Pricing tab is authoritative.

Input

FieldTypeDefaultDescription
projectZipStorestringnoneThe key-value store that holds your project ZIP. In Console, pick it with the store picker; in the API, send the store ID or name. The actor gets read access to this store only.
projectZipKeystringnoneThe record key of the ZIP in that store, for example project.zip. Required together with projectZipStore.
htmlstringprefilled 1-second example in ConsoleA complete single-file composition, up to 1,000,000 bytes. Ignored when projectZipStore and projectZipKey are set.
entryFilestringindex.htmlPath of the entry HTML file inside the ZIP, for example scenes/main.html. ZIP only.
formatstringmp4Output format. Only mp4 (H.264) is available.
fpsinteger30Frames per second: 24, 30 or 60.
qualitystringstandardEncoder preset: draft, standard or high.
variablesobject{}JSON object passed to your composition as HyperFrames variables, up to 64 KB.

Give exactly one source: a ZIP (store plus key) or inline HTML. If you give both, the ZIP is used, the HTML is ignored, and the run adds this warning: "Both a project ZIP and inline HTML were given; the ZIP was used and the HTML was ignored."

What your composition needs

  • A root element with data-composition-id.
  • data-width and data-height on the root, as even numbers. They set the MP4's size: at least 16 px, long edge up to 1920, short edge up to 1080.
  • data-duration in seconds on the root, up to 120. It's optional, but it lets the price check run before any browser starts.
  • Only relative paths to files inside the project. Public web assets (fonts, images, video) over the internet are fine.

Example: inline HTML (1 s, 320×180)

This is the prefilled example. It fits the smallest maximum cost per run.

<!DOCTYPE html>
<html><body>
<div data-composition-id="demo" data-width="320" data-height="180" data-duration="1" data-no-timeline data-fps="30"
style="width:320px;height:180px;background:#222;color:#fff;display:flex;align-items:center;justify-content:center;">
Hello
</div>
</body></html>

As actor input:

{
"html": "<!DOCTYPE html>\n<html><body>\n<div data-composition-id=\"demo\" data-width=\"320\" data-height=\"180\" data-duration=\"1\" data-no-timeline data-fps=\"30\"\n style=\"width:320px;height:180px;background:#222;color:#fff;display:flex;align-items:center;justify-content:center;\">\n Hello\n</div>\n</body></html>",
"fps": 30,
"quality": "standard"
}

Example: project ZIP

{
"projectZipStore": "my-hyperframes-projects",
"projectZipKey": "project.zip",
"entryFile": "index.html",
"fps": 30,
"quality": "standard"
}

Example: 60 fps, high quality, with variables

{
"projectZipStore": "my-hyperframes-projects",
"projectZipKey": "promo.zip",
"fps": 60,
"quality": "high",
"variables": { "headline": "Spring sale", "price": "$29" }
}

Prepare a ZIP project

  1. Put your entry file at the root of the ZIP as index.html, or set entryFile to its path. To try it, save the inline example above as index.html and zip it.
  2. Keep every local asset inside the ZIP and reference it with a relative path.
  3. Upload the ZIP to a key-value store:
    • In Console: open Storage → Key-value stores, create or open a store, and upload the ZIP as a record.
    • With the API: replace <storeId> with your store's ID.
curl -X PUT \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/zip" \
--data-binary @project.zip \
https://api.apify.com/v2/key-value-stores/<storeId>/records/project.zip

ZIP limits: 100 MiB compressed, 500 MiB extracted and 2,000 files. Playlist files (.m3u8, .m3u, .mpd, .ffconcat, .concat) and symlinks aren't allowed.

Limits

LimitValue
OutputMP4 (H.264) only, one video per run
SizeUp to 1920×1080, landscape or portrait; at least 16 px per side; even numbers
DurationUp to 120 s
Frame rate24, 30 or 60 fps
Inline HTML1,000,000 bytes
Variables64 KB
ZIP100 MiB compressed, 500 MiB extracted, 2,000 files, no playlist files or symlinks, relative paths only
External downloads200 MiB per run; private and internal network addresses are blocked
Memory8 GB to 16 GB; 16 GB is the default and recommended

Cost budget. Each run may use platform resources (compute and data transfer) worth at most what its price covers. Compositions that would take longer, or download more than the price covers, are stopped early with RENDER_COST_LIMIT and not charged. Reduce fps, quality, duration or external downloads, or keep the default 16 GB memory.

Output

Every run writes to its default storages:

WhereNameWhenWhat it is
Key-value storerender.mp4Success onlyThe video (H.264, yuv420p)
Key-value storeOUTPUTAlways (success, failure or abort)Status record: succeeded, failed or aborted, whether the run was charged, and the error code on failure
Datasetone itemSuccess onlyDuration, size, frames, expected charge, timings, warnings and the MP4 link

The run's status message tells you the result at a glance, for example Rendered 320x180, 1 s, … or Failed [EXPECTED_CHARGE_EXCEEDS_MAX]: ….

Dataset item (from a real 1-second run, signature masked)

{
"status": "succeeded",
"videoUrl": "https://api.apify.com/v2/key-value-stores/dGY54mBAoHpV9H3tl/records/render.mp4?signature=<signature>",
"videoKey": "render.mp4",
"format": "mp4",
"durationSeconds": 1,
"width": 320,
"height": 180,
"fps": 30,
"quality": "standard",
"totalFrames": 30,
"frameUnits": 1,
"expectedChargeUsd": 0.10500000000000001,
"sizeBytes": 3079,
"renderTimeMs": 6928,
"totalRunTimeMs": 18764,
"memoryMbytes": 16384,
"workers": 3,
"estimatedComputeUnits": 0.08339555555555556,
"budgetComputeUnits": 0.4037625000000001,
"externalBytes": 0,
"blockedRequests": 0,
"warnings": [],
"producerVersion": "0.8.104",
"chromeVersion": "148.0.7778.167"
}

OUTPUT on success has the same fields plus runId, charged (true or false) and chargedEvents, for example { "render-completed": 1, "frames-30": 1 }.

OUTPUT when a run fails

{
"schemaVersion": 2,
"status": "failed",
"runId": "<runId>",
"charged": false,
"error": {
"code": "EXPECTED_CHARGE_EXCEEDS_MAX",
"class": "user",
"phase": "precheck",
"message": "Expected charge $0.70 (render-completed $0.10 + 120 x frames-30 $0.005) exceeds the remaining maximum cost per run ($0.50). Raise the maximum cost per run, or reduce duration or fps."
},
"frames": { "fps": 30, "quality": "standard", "totalFrames": 3600, "frameUnits": 120, "expectedChargeUsd": 0.70 },
"producerVersion": "0.8.104",
"chromeVersion": "148.0.7778.167"
}

Look up error.code in the error table below for what it means and how to fix it.

Downloading the MP4

curl -L -o render.mp4 \
-H "Authorization: Bearer $APIFY_TOKEN" \
"https://api.apify.com/v2/key-value-stores/<defaultKeyValueStoreId>/records/render.mp4"

Signed links. videoUrl is normally a signed link. Signed links do not expire, so anyone who has the link can download the MP4. Share it as you would share the file.

Data retention

Each run's default storages are unnamed. Unnamed storages follow your plan's data retention and may be deleted after 7 days; on the Free plan the 10 most recent runs are kept for 4 months. Download the MP4, or copy it to a named key-value store, if you need to keep it.

Use cases

  • Personalised videos. Build one composition, then render a version per customer, product or event by changing variables.
  • Automated social clips. Generate vertical 1080×1920 or square videos from templates on a schedule.
  • AI agent pipelines. Let an agent write HyperFrames HTML and call this actor to turn it into a video file.
  • No-code workflows. Start renders from Make, Zapier or n8n through Apify integrations and webhooks, and pick up the MP4 link when the run finishes.
  • Data and product videos. Turn reports, release notes or listings into short explainer videos without opening a video editor.

Use it from your code

Replace APIFY_TOKEN with your token from Settings → API & Integrations in Apify Console. The Store's API tab has more clients and an OpenAPI definition.

Node.js

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('optimdex/hyperframes-render').call({
projectZipStore: 'my-hyperframes-projects',
projectZipKey: 'project.zip',
fps: 30,
quality: 'standard',
});
// Success: one dataset item with the MP4 link
const { items } = await client.dataset(run.defaultDatasetId).listItems();
if (items.length > 0) {
console.log('MP4:', items[0].videoUrl);
} else {
// Failure or abort: OUTPUT holds the error code
const output = await client.keyValueStore(run.defaultKeyValueStoreId).getRecord('OUTPUT');
console.log('Failed:', output?.value);
}

cURL (short renders)

This waits for the run and returns the dataset item. Apify's synchronous endpoints wait up to 5 minutes, so for long renders start a normal run and read the results when it finishes.

curl -X POST \
"https://api.apify.com/v2/acts/optimdex~hyperframes-render/run-sync-get-dataset-items?maxTotalChargeUsd=0.11" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"html": "<!DOCTYPE html><html><body><div data-composition-id=\"demo\" data-width=\"320\" data-height=\"180\" data-duration=\"1\" data-no-timeline data-fps=\"30\" style=\"width:320px;height:180px;background:#222;color:#fff;display:flex;align-items:center;justify-content:center;\">Hello</div></body></html>"
}'

Error codes

A failed run never charges for rendering; you pay only the start fee. The code appears in the run's status message as Failed [CODE]: … and in OUTPUT.error.code. User errors are fixable in your input or composition (exit code 2). System errors are on our side (exit code 3); if one repeats, report it in the Issues tab with the run ID.

Input

CodeWhat it meansHow to fix it
INPUT_INVALIDA field has the wrong type or value. Common causes: empty html, a ZIP store without a key (or a key without a store), a store or key that looks like a URL, a bad entryFile, an unknown quality, variables that isn't a JSON object, or entryFile other than index.html with inline HTML.Read the message for the field name. Give the store and key together, use only the record key (not a URL), and leave entryFile at index.html when using inline HTML.
INPUT_UNKNOWN_FIELDThe input has a field this actor doesn't accept, such as width, height or archiveUrl.Remove the listed fields. Size comes from the composition's data-width and data-height.
INPUT_SOURCE_MISSINGNeither a ZIP (store plus key) nor html was given.Add one source.
FORMAT_UNSUPPORTEDformat isn't mp4.Use mp4.
FPS_UNSUPPORTEDfps isn't 24, 30 or 60.Use 24, 30 or 60.
HTML_TOO_LARGEhtml is over 1,000,000 bytes.Move assets out of the HTML and send a ZIP instead.
VARIABLES_TOO_LARGEvariables is over 64 KB as JSON.Send less data, or put large data in a file inside the ZIP.

ZIP project

CodeWhat it meansHow to fix it
ZIP_RECORD_NOT_FOUNDThe store has no record with that key.Check the key's spelling and case, and that you picked the right store.
ZIP_ACCESS_DENIEDThe actor isn't allowed to read that store.In Console, pick the store with the store picker so the actor gets read access. Through the API, use a store your token's account owns.
ZIP_TOO_LARGEThe ZIP is over 100 MiB.Compress or shrink assets, or load large media from a public URL instead.
ZIP_INVALIDThe file isn't a valid ZIP, or it's corrupt, encrypted or uses an unsupported compression method.Re-create it with a standard zip tool, without a password.
ZIP_UNSAFE_PATHAn entry has an absolute path, .., a backslash, or a path that's too long, too deep or duplicated.Zip from inside the project folder so every path is relative, and use forward slashes.
ZIP_SYMLINKThe ZIP contains a symlink or another special file.Replace symlinks with real files.
ZIP_TOO_MANY_FILESThe ZIP has more than 2,000 entries.Remove unused files or combine assets.
ZIP_EXTRACTED_TOO_LARGEThe contents are over 500 MiB once extracted, or the compression ratio looks like a ZIP bomb.Reduce asset sizes.
ZIP_UNSUPPORTED_FILEThe ZIP contains a playlist file (.m3u8, .m3u, .mpd, .ffconcat or .concat).Remove playlist files and use regular video files instead.
ENTRY_FILE_NOT_FOUNDThe entry HTML file isn't in the ZIP.Put index.html at the ZIP root, or set entryFile to the right path.

Composition

CodeWhat it meansHow to fix it
COMPOSITION_INVALIDThe root element is missing data-composition-id, data-width or data-height is missing, odd or invalid, or the duration isn't positive.Add data-composition-id, use even width and height, and give a positive data-duration.
COMPOSITION_UNSAFE_PATHA src, href, poster, data-composition-src or data-composition-file uses an absolute path or file: link, or points outside the project, or a sub-composition isn't an .html file. Sub-compositions are checked too.Use relative paths that stay inside the project, and point sub-compositions at .html files.
RESOLUTION_LIMIT_EXCEEDEDThe long edge is over 1920 or the short edge is over 1080.Use 1920×1080, 1080×1920 or smaller.
DURATION_LIMIT_EXCEEDEDThe composition is longer than 120 s.Shorten it, or split it across several runs.

Cost and time limits

CodeWhat it meansHow to fix it
EXPECTED_CHARGE_EXCEEDS_MAXThe render would cost more than your maximum cost per run.Raise the maximum cost per run (maxTotalChargeUsd in the API) using the cost examples, or reduce duration or fps.
EXTERNAL_DOWNLOAD_LIMITThe composition downloaded 200 MiB from the internet.Put large media in the ZIP, or use smaller files.
RENDER_COST_LIMITThe render was too slow, or downloaded too much, for its price.Lower fps, quality, duration or external downloads, simplify heavy effects, or keep the default 16 GB memory.
RENDER_TIMEOUTA render step or the run hit its time limit.Raise the run timeout, simplify the composition, or shorten it.
RENDER_STALLEDNo new frame for 60 s, or no first frame within 180 s. Often an endless loop in the composition's script, or an asset that never loads.Check the composition renders in a normal browser, and remove scripts that wait forever.
RUN_TIMEOUT_TOO_SHORTThe run timeout leaves less than 2 minutes to render.Raise the run timeout. 2,400 s covers every render this actor allows.
OUTPUT_TOO_LARGEThe MP4 came out larger than the size limit: 150 KB per frame (about 4.5 MB per second at 30 fps), and never more than 512 MiB.Use standard or draft quality, or reduce noisy, high-detail motion.

System

CodeWhat it meansHow to fix it
RENDER_FAILEDThe renderer or browser failed.Try again. If it repeats, report it with the run ID.
OUTPUT_VERIFY_FAILEDThe finished MP4 didn't pass the final check (wrong size, frame rate or duration).Try again. If it repeats, report it with the run ID.
STORAGE_WRITE_FAILEDThe MP4 couldn't be saved to storage.Try again.
RESTART_LIMIT_EXCEEDEDThe platform moved the run to a new server more than 3 times.Try again.
INTERNAL_ERRORSomething unexpected went wrong.Try again. If it repeats, report it with the run ID.

FAQ

What is HyperFrames?

HyperFrames is an open-source framework from HeyGen for writing videos as HTML, licensed under Apache-2.0. This actor runs its renderer for you in the cloud. It's an independent, unofficial service.

Do I need a HeyGen account?

No. You need only an Apify account.

Does resolution or quality change the price?

No. Price depends only on the number of frames: duration times fps. A 30-second video costs $0.25 at 30 fps whether it's 320×180 or 1920×1080, and whether quality is draft or high.

Am I charged when a run fails?

Only Apify's start fee ($0.0008 at 16 GB). Rendering is charged only when the MP4 is saved. Renders that finish with warnings are charged, and the warnings are listed in the output.

Can I render several videos in one run?

No, it's one video per run. Start several runs in parallel, each with its own input or variables.

How long does a render take?

The 1-second example finishes in about 20 seconds. Longer and heavier compositions take longer, and each run must finish within what its price covers (see Cost budget under Limits).

Can my composition load fonts, images or video from the web?

Yes, from public internet addresses, up to 200 MiB per run. Private and internal network addresses are blocked, and blocked requests are counted in blockedRequests. If an asset fails to load, the render usually still completes and a warning is added.

I gave both a ZIP and inline HTML. Which one is used?

The ZIP. The HTML is ignored and a warning is added to the output.

How do I keep my videos?

Download render.mp4, or copy it to a named key-value store. Unnamed run storages may be deleted after 7 days, depending on your plan.

videoUrl is normally a signed link that never expires, so anyone with the link can download the MP4. Share it as you would share the file itself.

Is this an official HeyGen product?

No. Unofficial. Not affiliated with, endorsed by, or sponsored by HeyGen. HyperFrames is a project of HeyGen, Inc.

License and attribution

This actor is proprietary; its source code isn't public. It bundles third-party software under each project's own licence: