HopShot

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

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.

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

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.