Developers
RecordHTML MCP server
Connect RecordHTML to your AI assistant and ask it for videos. The hosted MCP server records web pages and HTML5 creatives to MP4, WebM or GIF with the same real-browser recorder, limits and storage as the web app and the API.
Coding agents with a shell can use the REST API too: its reference is plain Markdown at /docs/api.md, listed in /llms.txt. This page is also Markdown.
Connect
Add a remote MCP server with this URL. It speaks Streamable HTTP, and you sign in with OAuth: there is no key to paste and nothing to run on your machine.
https://mcp.recordhtml.com- Use the root URL as shown.
https://mcp.recordhtml.com/mcpworks too; there is no/sseendpoint. - Your client opens a sign-in page: sign in or create an account, approve the access and go back to the client.
- Check that the client lists five tools:
record_html,get_recording,estimate_recording,list_recordingsanddelete_recording. - The client must support remote MCP servers with OAuth. Otherwise, use an API key.
Set up your client
Claude (claude.ai and Claude Desktop)
- Open Settings → Connectors and choose Add custom connector.
- Name it RecordHTML, enter
https://mcp.recordhtml.comas the URL and add it. - Click Connect and sign in. On Team and Enterprise plans an owner adds the connector for the organization first; each member then connects their own account.
Claude Code
- Add the server, then type
/mcpin Claude Code, choose recordhtml and sign in in the browser.
claude mcp add --transport http recordhtml https://mcp.recordhtml.comChatGPT
- In Settings → Apps & Connectors → Advanced settings, turn on Developer mode.
- Create a connector with
https://mcp.recordhtml.comas the URL and OAuth as the authentication, and sign in. - Pick the connector in a chat that has developer mode on.
Codex
- Codex sees that the server uses OAuth and opens the sign-in page. To sign in again later, run
codex mcp login recordhtml.
codex mcp add recordhtml --url https://mcp.recordhtml.comCursor
- Add the server to
~/.cursor/mcp.json(or.cursor/mcp.jsonin a project). Cursor asks you to sign in the first time it uses it.
{
"mcpServers": {
"recordhtml": {
"url": "https://mcp.recordhtml.com"
}
}
}VS Code
- Add the server to
.vscode/mcp.json(or run MCP: Add Server), start it and sign in when VS Code asks. Turn its tools on in the chat tool picker.
{
"servers": {
"recordhtml": {
"type": "http",
"url": "https://mcp.recordhtml.com"
}
}
}Other clients, or an API key
- Any client that supports remote MCP servers with OAuth works with the URL alone.
- Headless agents, and clients that can set headers, can send an API key from the API settings page instead:
Authorization: Bearer rh_live_…. Keys are refused from browser pages. - Cursor ignores headers for a server that offers OAuth: sign in there instead.
claude mcp add --transport http recordhtml https://mcp.recordhtml.com \
--header "Authorization: Bearer $RECORDHTML_API_KEY"Your first recording
Ask for a video in plain words; the assistant fills in the arguments.
Record https://example.com/banner/ at 300×600 for 15 seconds as an MP4.record_html { "url": "https://example.com/banner/", "width": 300, "height": 600, "durationSec": 15, "format": "mp4" }Recording 4c1f0e1e-8f0a-4a52-9d1b-2f5f3c6b9a10 is ready.
Video: https://…/banner.mp4?…
(signed link, valid until 2026-10-22T10:14:52.004Z; get_recording mints a fresh one)
300×600, 30 fps, 15.0 s, mp4, 1.8 MB
Started at the load event.
Page (for a person; they sign in): https://…/jobs/4c1f0e1e-8f0a-4a52-9d1b-2f5f3c6b9a10The result also carries the same data as structured content (the job of the API, with pageUrl) and a link to the video file. To save the file from a shell: curl --fail --location --output banner.mp4 "<video url>".
Tools
record_htmlRecord HTML to video
What the model reads
Record a web page or HTML5 creative (banner, ad, animation, landing page) to a video: MP4 (default), WebM or GIF. Returns the finished recording with a video link. Input: either url (a public http(s) page; localhost and private networks are out of reach) or html (a complete single-file HTML document you wrote, published at an unlisted public URL and recorded from there). Set width and height to the creative's size (e.g. 300×250, 728×90, 1080×1920, 1920×1080) and durationSec to how long it runs. Waiting: the call waits up to waitSec (default 45 s) for the video. If it isn't done by then, you get the recording with status queued or running; it keeps going. Call get_recording with its id and waitSec to wait for it; don't call record_html again for it. Retries are safe: identical calls within 10 minutes return the same recording. After changing the creative behind the same url, pass a new revision to record it again. A failed recording is never reused: call again once you've fixed the cause. Trim markers: by default the video starts at the page's load event and lasts durationSec. To capture exactly the animation, the page can mark it. The recorder defines two global functions in every frame (iframes included): - trim_start(): call it once when the assets are loaded, just before the animation starts. The video starts there. - trim_end(): call it once from the animation's completion callback (e.g. GSAP onComplete, the last animationend). The video ends there and durationSec becomes the maximum. Guard both so the page still works in a normal browser: // assets loaded, just before the animation starts: if (typeof trim_start === 'function') trim_start(); // in the animation's completion callback: if (typeof trim_end === 'function') trim_end(); Never call the two right after each other: that marks an empty clip. With sync "markers" the recording fails instead of falling back to the load event when trim_start() isn't called within markerWaitSec (default 10 s). report.anchor says "trim_start" when the start marker was used. Links: video.url is signed and valid for 1 hour (get_recording mints a fresh one). pageUrl is the recording's page in the web app, for a person to open later (they sign in). Limits: the account's limits apply (length, size, fps, recordings at a time and per day); estimate_recording checks a request without recording. Errors say what to change: fix it and call again.
| Parameter | Type | Description |
|---|---|---|
| url | string | Public http(s) URL of the page or creative to record. The recorder runs on the internet: localhost and private networks are out of reach. Pass url or html, not both. |
| width | integer, required | Viewport width in CSS pixels: even, 16–7680. Use the creative’s size, e.g. 300 for a 300×250 banner or 1920 for a full HD page. |
| height | integer, required | Viewport height in CSS pixels: even, 16–7680. |
| durationSec | number, required | Length of the video in seconds (1–300; your account limit applies). With trim_end() it is the maximum. |
| fps | number | string | Frame rate: one of 15, 23.976, 24, 25, 29.97, 30, 59.94, 60 (default 30). For gif, a whole number from 1 to 30. |
| startAt | "load" | integer | Where the video starts: "load" (default: the load event, or the first trim_start() call) or that many milliseconds after it (0–120000). |
| sync | "auto" | "load" | "markers" | How trim markers are used: auto (default: markers when the page calls trim_start(), else the load event), load (ignore markers) or markers (fail when trim_start() is not called within markerWaitSec). |
| markerWaitSec | number | How long to wait for trim_start() after load, in seconds (0–60, default 10). |
| markerLatencyMs | number | Overrides the measured delay between a marker call and the frame it marks (0–1000 ms). Rarely needed. |
| format | "mp4" | "webm" | "gif" | mp4 (default, H.264), webm or gif. |
| crf | integer | Quality for mp4 and webm, from 10 (best, largest) to 35 (smallest). Not together with bitrateMbps; not for gif. |
| bitrateMbps | number | A target bitrate in Mbps (1–100) instead of crf, for platforms with a bitrate spec (e.g. DOOH). Not for gif. |
| bitrateMode | "vbr" | "cbr" | vbr (default) or cbr. Needs bitrateMbps. |
| pixelFormat | "yuv420p" | "yuv422p" | "yuv444p" | yuv420p (default; plays everywhere), yuv422p or yuv444p. |
| audio | boolean | Record the page’s audio too. Not for gif. |
| loop | boolean | gif only: whether the gif loops. |
| crop | object | Record only this region of the viewport: { x, y, width, height } in pixels, even values, inside the viewport. |
| outputSize | object | Scale the video to { width, height } (even values); see fit. |
| fit | "fill" | "contain" | "cover" | How the frame fills outputSize: fill (stretch), contain (letterbox in background) or cover (crop to fill). |
| background | string | Letterbox colour for fit: contain, as #rrggbb. |
| renderer | "auto" | "cpu" | "gpu" | auto (default), cpu or gpu. Leave it on auto. |
| html | string | A complete HTML document to record instead of a url, e.g. a banner you just wrote. One file: inline the CSS and JavaScript, and load images, fonts and libraries from public https URLs or data: URIs. It is published at an unlisted public URL and recorded from there. |
| revision | string | Any label that changes when the creative changes. Identical calls within 10 minutes return the same recording; after you change the creative behind the same url, pass a new revision to record it again. |
| waitSec | integer | How long to wait for the finished video, in seconds (0–45, default 45). The recording keeps going after the call returns. |
get_recordingGet a recording
read-only
What the model reads
Get a recording's status, report and a fresh video link (signed, valid for 1 hour). With waitSec, waits up to that many seconds (at most 45) for a queued or running recording to finish. Use it to continue after record_html returned before the video was ready, or to refresh an expired link.
| Parameter | Type | Description |
|---|---|---|
| id | string, required | The recording’s id, from record_html or list_recordings. |
| waitSec | integer | How long to wait for the finished video, in seconds (0–45, default 0). The recording keeps going after the call returns. |
estimate_recordingCheck a recording request
read-only
What the model reads
Check a recording request without recording or using up any quota. Takes the same arguments as record_html. Returns what to fix (invalidIssues, limitIssues, capacity.reasons), the resource estimate, your limits and your usage: recordings in progress and recordings started in the last 24 hours.
| Parameter | Type | Description |
|---|---|---|
| url | string | Public http(s) URL of the page or creative to record. The recorder runs on the internet: localhost and private networks are out of reach. Pass url or html, not both. |
| width | integer, required | Viewport width in CSS pixels: even, 16–7680. Use the creative’s size, e.g. 300 for a 300×250 banner or 1920 for a full HD page. |
| height | integer, required | Viewport height in CSS pixels: even, 16–7680. |
| durationSec | number, required | Length of the video in seconds (1–300; your account limit applies). With trim_end() it is the maximum. |
| fps | number | string | Frame rate: one of 15, 23.976, 24, 25, 29.97, 30, 59.94, 60 (default 30). For gif, a whole number from 1 to 30. |
| startAt | "load" | integer | Where the video starts: "load" (default: the load event, or the first trim_start() call) or that many milliseconds after it (0–120000). |
| sync | "auto" | "load" | "markers" | How trim markers are used: auto (default: markers when the page calls trim_start(), else the load event), load (ignore markers) or markers (fail when trim_start() is not called within markerWaitSec). |
| markerWaitSec | number | How long to wait for trim_start() after load, in seconds (0–60, default 10). |
| markerLatencyMs | number | Overrides the measured delay between a marker call and the frame it marks (0–1000 ms). Rarely needed. |
| format | "mp4" | "webm" | "gif" | mp4 (default, H.264), webm or gif. |
| crf | integer | Quality for mp4 and webm, from 10 (best, largest) to 35 (smallest). Not together with bitrateMbps; not for gif. |
| bitrateMbps | number | A target bitrate in Mbps (1–100) instead of crf, for platforms with a bitrate spec (e.g. DOOH). Not for gif. |
| bitrateMode | "vbr" | "cbr" | vbr (default) or cbr. Needs bitrateMbps. |
| pixelFormat | "yuv420p" | "yuv422p" | "yuv444p" | yuv420p (default; plays everywhere), yuv422p or yuv444p. |
| audio | boolean | Record the page’s audio too. Not for gif. |
| loop | boolean | gif only: whether the gif loops. |
| crop | object | Record only this region of the viewport: { x, y, width, height } in pixels, even values, inside the viewport. |
| outputSize | object | Scale the video to { width, height } (even values); see fit. |
| fit | "fill" | "contain" | "cover" | How the frame fills outputSize: fill (stretch), contain (letterbox in background) or cover (crop to fill). |
| background | string | Letterbox colour for fit: contain, as #rrggbb. |
| renderer | "auto" | "cpu" | "gpu" | auto (default), cpu or gpu. Leave it on auto. |
| html | string | A complete HTML document to record instead of a url, e.g. a banner you just wrote. One file: inline the CSS and JavaScript, and load images, fonts and libraries from public https URLs or data: URIs. It is published at an unlisted public URL and recorded from there. |
list_recordingsList recordings
read-only
What the model reads
List your recordings, newest first: id, status, request, report and pageUrl (10 per page by default). Video links are left out; call get_recording for one. Continue with nextCursor.
| Parameter | Type | Description |
|---|---|---|
| status | string | "active" (queued, running or storing) or a comma list of queued, running, storing, ready, failed, deleted, expired. Default: everything except deleted. |
| limit | integer | How many to return (1–50, default 10). |
| cursor | string | nextCursor from the previous page. |
delete_recordingDelete a recording
destructiveidempotent
What the model reads
Delete a recording’s video files for good: the original and any crop or trim. The recording stays in the history with status deleted. This cannot be undone; deleting it again changes nothing.
| Parameter | Type | Description |
|---|---|---|
| id | string, required | The recording’s id, from record_html or list_recordings. |
Waiting and retries
record_htmlwaits up to 45 seconds (waitSec) for the video, and reports progress to clients that show it. A longer recording comes back with its id and statusqueuedorrunning, and keeps going:get_recordingwithwaitSecpicks it up.- Identical
record_htmlcalls within 10 minutes return the same recording, so a retried call never records twice. After changing the creative behind the same URL, pass a newrevision. A failed recording is never reused. - Stopping a call (or a client timeout) never stops the recording.
Trim markers
An assistant that writes the creative can mark exactly what to record. The recorder defines two global functions in every frame, iframes included:
// assets loaded, just before the animation starts:
if (typeof trim_start === 'function') trim_start();
// in the animation's completion callback:
if (typeof trim_end === 'function') trim_end();trim_start(): once, when the assets are loaded and just before the animation starts. The video starts there.trim_end(): once, from the animation's completion callback. The video ends there, anddurationSecbecomes the maximum.- Never call the two right after each other: that marks an empty clip.
- With
sync: "markers"the recording fails whentrim_start()isn't called withinmarkerWaitSec, instead of starting at the load event.
Every option is in the API reference.
Links and storage
video.urlis a signed link to the file, valid for one hour;get_recordingmints a fresh one.pageUrlis the recording's page in the web app, where a person can watch, download, crop or trim it later (they sign in).- From code,
GET /v1/jobs/{id}/videowith an API key always redirects to a fresh link. - Recordings made through MCP are listed and kept with all your other recordings.
- Inline
htmlis published at an unlisted public URL so the recorder can load it. Don't put secrets in it.
Limits and errors
MCP, the API and the web app share your account's limits: recordings in progress at a time, recordings per 24 hours, and the maximum duration, size and frame rate. estimate_recording shows them with your usage. Each account can also call record_html 20 times a minute and estimate_recording 60 times a minute.
Errors come back as tool errors the assistant can act on: the code, the message, one line per problem and what to do next.
limit_exceeded: this job is beyond your limits
- durationSec: your limit is 60 s
Lower the values listed (estimate_recording shows the account limits) and call again.The codes are those of the API.
Manage access
- To disconnect a client, remove the server or connector in it; that deletes its tokens.
- An API key used with MCP can be revoked at once on the API settings page.
Troubleshooting
| Problem | What to do |
|---|---|
| The client can’t connect | Use https://mcp.recordhtml.com with Streamable HTTP. The client must support OAuth for remote servers, or send an API key. |
| The page can’t be reached | The recorder runs on the internet: localhost and private addresses are out of reach. Publish the page, or let the assistant pass the HTML as html. |
| uploads_disabled | This server can’t publish inline HTML: pass a public url. |
| too_many_active_jobs | Your recordings in progress are at the limit: wait for one (get_recording with waitSec). |
| The client stopped waiting | Some clients give up on a call after about a minute. The recording carries on: ask the assistant to call get_recording. |
| The link expired | Signed links last an hour: get_recording mints a fresh one. |