Connect and render
Send HTML and receive a PNG through the API or MCP. No account or API key is required.
API quick start
POST JSON to https://hopshot.dev/api/v1/render.
curl --fail-with-body --silent --show-error \
--header 'Content-Type: application/json' \
--dump-header render.headers \
--output render.png \
--data-binary @- \
'https://hopshot.dev/api/v1/render' <<'JSON'
{
"html": "<div class=\"flex h-full w-full items-center justify-center bg-[#112233] text-6xl text-white\">Hello World</div>",
"width": 1200,
"height": 630
}
JSON
A successful request saves render.png and the response headers in render.headers. Expect HTTP 200 and Content-Type: image/png. On an HTTP error, curl exits non-zero and saves the error body instead of a PNG.
Request fields
html— required string containing static HTML.width— optional integer in pixels; defaults to 1200.height— optional integer in pixels; defaults to 630.
JSON payload
{
"html": "<div class=\"flex h-full w-full items-center justify-center bg-[#112233] text-6xl text-white\">Hello World</div>",
"width": 1200,
"height": 630
}
The API sends raw PNG bytes, not base64 or a file URL. Rendering completes before the response is streamed, so a failed render can still return an error status. Images are not saved by HopShot.
Connect an MCP client
Add https://hopshot.dev/mcp as a remote MCP server using Streamable HTTP. Leave authentication unset. Clients that accept an mcpServers configuration can use:
{
"mcpServers": {
"hopshot": {
"type": "http",
"url": "https://hopshot.dev/mcp"
}
}
}
Discover the tools and call render_image with the same html, width, and height fields as the API. The client handles initialization and protocol negotiation.
Test MCP from a terminal
This direct tool call uses protocol version 2025-11-25 and returns JSON:
curl --fail-with-body --silent --show-error \
--header 'Content-Type: application/json' \
--header 'Accept: application/json, text/event-stream' \
--header 'MCP-Protocol-Version: 2025-11-25' \
--data-binary @- \
'https://hopshot.dev/mcp' <<'JSON'
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "render_image",
"arguments": {
"html": "<div class=\"flex h-full w-full items-center justify-center bg-[#112233] text-6xl text-white\">Hello World</div>",
"width": 1200,
"height": 630
}
}
}
JSON
Tool call JSON
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "render_image",
"arguments": {
"html": "<div class=\"flex h-full w-full items-center justify-center bg-[#112233] text-6xl text-white\">Hello World</div>",
"width": 1200,
"height": 630
}
}
}
On success, result.isError is false. The image content has type: image, mimeType: image/png, and base64 PNG bytes in data. Decode those bytes to save a file; they are not an image URL. Streamable HTTP is the MCP transport, not an incremental PNG stream.
Supported markup and limits
Use static text and layout elements with inline CSS, plain rules in <style> tags, or common Tailwind classes in class attributes. Text uses bundled Instrument Sans. Takumi is not a browser; not every CSS feature or Tailwind utility is supported.
Scripts, event handlers, images, SVG, external assets, custom fonts, CSS URLs, at-rules, comments, and CSS escapes are rejected. No remote resources are fetched.
- HTML: up to 256 KiB. HTTP request body: up to 2 MiB.
- Dimensions: 1–4096 pixels per side, up to 8,000,000 pixels total.
- HTML structure: at most 2000 nodes and 64 levels of nesting.
- PNG output: up to 8 MiB. Render timeout: 10 seconds.
- Anonymous requests: 30 per minute per IP, shared across the API and HTTP MCP. MCP initialization, discovery, and tool calls share the quota, including failed renders.
- Up to 2 renders at once across the service.
Read X-RateLimit-Limit and X-RateLimit-Remaining on responses. For HTTP 429, wait for the number of seconds in Retry-After before trying again.
Errors and retries
- 400 — malformed JSON. Send a valid JSON object.
- 413 — request body too large. Reduce the payload.
- 415 — unsupported content type. Send
application/json. - 422 — invalid fields, unsupported markup, or oversized output. Check the message and adjust the request.
- 429 — request quota reached (
rate_limited) or all render slots busy (render_busy). RespectRetry-After. - 500 / 503 — rendering failed or the renderer is unavailable. Retry later; persistent failures need service-side investigation.
- 504 — render timeout. Simplify the HTML or reduce the dimensions.
API field validation errors contain message and errors, keyed by field. Other application errors contain error.code and error.message.
MCP tool failures can return HTTP 200 with result.isError: true and an explanation in text content. Check that flag before reading image data. HTTP-level failures such as throttling use the status codes above.