Pagecorder is an API that loads your URL in a browser, captures the screen and audio at 60 FPS, and delivers an MP4. Generate video from HTML, CSS, and JavaScript programmatically.
Add two lines of JavaScript to your page. Pagecorder does the rest.
Call window.pagecorder('start') when your page is ready to begin recording, and
window.pagecorder('stop') when it should end. These calls are silently ignored in normal
browsers, so your page works everywhere.
<script>
// Start recording when the page loads
window.pagecorder('start');
// ... your animation or dynamic content runs ...
// Stop after 10 seconds
setTimeout(() => window.pagecorder('stop'), 10000);
</script>
POST the URL of your page (or a ZIP archive containing it) to the Pagecorder API. You get back a job ID immediately.
POST /rapid/render
{
"url": "https://files.example.com/my-animation.zip",
"width": 1920,
"height": 1080
}
Poll the job status endpoint until the recording is done. The result includes the URL of your MP4 file, recording duration, and diagnostic information.
GET /rapid/render/42
{
"state": { "stage": "done" },
"result": {
"url": "https://cdn.example.com/recordings/42.mp4",
"durationSeconds": 29,
"droppedFrames": [],
"httpErrors": []
}
}
The recommended way to use Pagecorder is to bundle your entire website into a ZIP archive. The archive is downloaded and served locally during recording, which means faster loading, no network errors, and fully reproducible results.
No network requests during recording. Assets load from local disk instantly.
All assets (images, fonts, scripts) are available locally. No failed HTTP requests.
Results are not affected by CDN latency, rate limiting, or transient network issues.
my-animation.zip
index.html // Entry point (required)
styles.css
script.js
assets/
logo.png
font.woff2
Host the ZIP anywhere with a public URL and pass it as the
url parameter. You can also pass a regular text/html URL for quick tests.
Two endpoints. Submit a job, poll for the result.
Submit a new recording job. Returns the job object immediately.
| Field | Type | Default | Description |
|---|---|---|---|
url |
string | -- | URL to record (text/html or .zip). Required. |
width |
number | 1920 | Viewport width (1-3840 px). |
height |
number | 1080 | Viewport height (1-2160 px). |
upload |
object | Server default | Custom FTP upload target. |
timeouts |
object | See docs | Per-stage timeout overrides (ms). |
Get the status of a recording job. Poll until state.stage is "done" or
failedReason is present.
| Stage | Default | Maximum | Description |
|---|---|---|---|
downloading |
5 min | 15 min | Download a ZIP archive. |
loading |
1 min | 15 min | Page load + pagecorder('start'). |
rendering |
1 hour | 6 hours | Recording between start and stop. |
uploading |
15 min | 1 hour | Upload MP4 to FTP. |
Billed per second of actual recording duration. Billing is triggered when you fetch a completed job for the first time. Failed jobs are not billed.
| Plan | Max unbilled jobs |
|---|---|
| BASIC | 1 |
| PRO | 3 |
| ULTRA | 5 |
| MEGA | 10 |
An "unbilled" job is any job in progress or completed but not yet fetched. Fetch your results to free up slots.
Every completed job includes diagnostic data to help you identify and fix issues.
The droppedFrames array reports frames that could not be rendered at 60 FPS, causing visual
stuttering. This means your page is too computationally expensive to render in real time.
{
"droppedFrames": [
{ "dropped": 3, "time": "00:12.450", "reason": "rendering too slow" }
]
}
Common causes: heavy CSS animations (box-shadow, blur filters), Canvas/WebGL operations exceeding the frame budget, large DOM reflows, or expensive synchronous JavaScript.
Fixes: use transform and opacity for animations, reduce viewport
size, simplify the DOM, or use a ZIP archive to eliminate network slowdowns.
The httpErrors array reports HTTP requests your page made that returned 4xx or 5xx status codes.
These indicate missing or inaccessible assets.
{
"httpErrors": [
{ "status": 404, "method": "GET", "url": "https://example.com/font.woff2" }
]
}
Fix: ensure all assets are publicly accessible, or use a ZIP archive to bundle everything locally and eliminate HTTP errors entirely.
The logs array captures console.log, console.warn, and
console.error messages from your page during recording.
const RAPIDAPI_KEY = "your-rapidapi-key";
const RAPIDAPI_HOST = "pagecorder.p.rapidapi.com";
// 1. Submit a recording job
const submitRes = await fetch(`https://${RAPIDAPI_HOST}/rapid/render`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-RapidAPI-Key": RAPIDAPI_KEY,
"X-RapidAPI-Host": RAPIDAPI_HOST,
},
body: JSON.stringify({
url: "https://files.example.com/animation.zip",
width: 1280,
height: 720,
}),
});
const { id } = await submitRes.json();
// 2. Poll until done
let result;
while (true) {
const res = await fetch(`https://${RAPIDAPI_HOST}/rapid/render/${id}`, {
headers: {
"X-RapidAPI-Key": RAPIDAPI_KEY,
"X-RapidAPI-Host": RAPIDAPI_HOST,
},
});
const job = await res.json();
if (job.state.stage === "done") {
result = job.result;
break;
}
if (job.failedReason) throw new Error(job.failedReason);
await new Promise(r => setTimeout(r, 30000));
}
console.log(`Recording: ${result.url}`);
console.log(`Duration: ${result.durationSeconds}s`);
import requests
import time
RAPIDAPI_KEY = "your-rapidapi-key"
RAPIDAPI_HOST = "pagecorder.p.rapidapi.com"
HEADERS = {
"X-RapidAPI-Key": RAPIDAPI_KEY,
"X-RapidAPI-Host": RAPIDAPI_HOST,
}
# 1. Submit a recording job
job = requests.post(
f"https://{RAPIDAPI_HOST}/rapid/render",
headers={**HEADERS, "Content-Type": "application/json"},
json={
"url": "https://files.example.com/animation.zip",
"width": 1280,
"height": 720,
},
).json()
job_id = job["id"]
# 2. Poll until done
while True:
job = requests.get(
f"https://{RAPIDAPI_HOST}/rapid/render/{job_id}",
headers=HEADERS,
).json()
if job["state"]["stage"] == "done":
result = job["result"]
print(f"Recording: {result['url']}")
print(f"Duration: {result['durationSeconds']}s")
break
if job.get("failedReason"):
raise Exception(f"Job failed: {job['failedReason']}")
time.sleep(30)