Private beta— the API is live and free. Grab a key below and start rendering.
RenderHelm.

Documentation

Render anything in three calls.

Authenticate with a key, then send a URL or HTML. Every endpoint returns the file directly — no polling, no webhooks, no SDK to install.

Quickstart

Create a free key (email-verified, 500 renders a month, no card), then make your first call. The response body is the image.

# a full-page screenshot, saved as PNG
curl -X POST https://renderhelm.keelhelm.com/v1/screenshot \
  -H "Authorization: Bearer sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","full_page":true}' \
  --output shot.png
// app/api/shot/route.ts — Next.js route handler
export async function GET() {
  const r = await fetch("https://renderhelm.keelhelm.com/v1/screenshot", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.RENDERHELM_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ url: "https://example.com" }),
  });
  return new Response(r.body, { headers: { "Content-Type": "image/png" } });
}
// src/pages/shot.png.ts — Astro endpoint
export const GET = async () =>
  fetch("https://renderhelm.keelhelm.com/v1/screenshot", {
    method: "POST",
    headers: { Authorization: `Bearer ${import.meta.env.RENDERHELM_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ url: "https://example.com" }),
  });

Authentication

Pass your secret key as a bearer token: Authorization: Bearer sk_…. Keys are created at signup and scoped to your account. For Open Graph images you embed in HTML, use your public key plus a signature (see below) so the secret never appears in a URL.

Screenshot

POST /v1/screenshot

Capture any URL or your own HTML as a PNG or JPEG. Viewport by default; full-page on request.

FieldTypeNotes
url / htmlstringOne is required (not both).
full_pagebooleanCapture the whole scroll height. Counts as 2 renders.
formatpng · jpegDefaults to png.
viewport_width / viewport_heightnumberDefaults 1280 × 800.
responsebinary · urlImage bytes, or a JSON link to the cached result.

PDF

POST /v1/pdf

Turn a live page or your own markup into a paginated PDF. Counts as 2 renders.

FieldTypeNotes
url / htmlstringOne is required.
formatA4 · LetterDefaults to A4.
landscapebooleanDefaults to false.
margin_mmnumberUniform page margin in millimetres.
curl -X POST https://renderhelm.keelhelm.com/v1/pdf \
  -H "Authorization: Bearer sk_your_key" -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","format":"A4"}' --output page.pdf

Render

POST /v1/render

Send your own HTML and get back a PNG, JPEG, or PDF. The HTML can be a finished document or a template with variables and loops — RenderHelm expands it server-side, then renders the result.

FieldTypeNotes
htmlstringRequired, up to 2 MB. May contain the template tags below.
varsobjectValues the template references.
formatpng · jpeg · pdfDefaults to png. PDF counts as 2 renders.
cssstring[]Up to 5 https stylesheet URLs, linked into the page.
css_idstringA stored private stylesheet — see Private CSS.
full_page · viewport_width · viewport_height · dark_mode · responseSame as Screenshot.

Template syntax

TagMeaning
{{ path.to.value }}Insert a value — HTML-escaped by default, so user data can't inject markup. Missing values render empty.
{{ value | raw }}Insert without escaping. Only use with content you trust| raw can inject arbitrary markup.
{% if path %}…{% else %}…{% endif %}Show a block when the value is truthy; {% if not path %} negates.
{% for item in list %}…{% endfor %}Repeat for each array item. loop.index, loop.first, and loop.last are available inside.

Per render: up to 50k tags, 20 levels of nesting, 10k total loop iterations, and 2 MB of output. A malformed template returns template_syntax_error; exceeding a limit returns template_limits_exceeded.

curl -X POST https://renderhelm.keelhelm.com/v1/render \
  -H "Authorization: Bearer sk_your_key" -H "Content-Type: application/json" \
  -d '{"html":"<h1>Hi {{ name }}</h1>","vars":{"name":"there"}}' --output card.png

Private CSS

POST /v1/css · Pro & Scale

Store your brand stylesheet once, then reference it from any render with css_id. Your styles are injected server-side, so they never appear in a URL and load instantly. Available on the Pro and Scale plans.

RouteDoes
POST /v1/cssCreate or overwrite: {"name":"brand","content":"…css…"}. Name is [a-z0-9_-], content up to 256 KB.
GET /v1/cssList your stored stylesheets.
DELETE /v1/css/:nameDelete one.

Up to 20 stylesheets per account. On a Free or Starter key these routes return tier_required; referencing a css_id that doesn't exist at render time returns css_not_found.

Open Graph image

GET /v1/og/:template.png

Generate the preview image that appears when a link is shared, from a reusable template. Because it's a plain GET, you embed it with a single tag — pass your public key and a signature so the URL is safe to ship in your HTML.

<!-- in your page <head> -->
<meta property="og:image"
  content="https://renderhelm.keelhelm.com/v1/og/blog-header.png?title=Hello&pk=pk_you&sig=…">

Browse the template gallery for every template, its parameters, and a live example. Identical parameters return instantly from cache.

Signed GET embeds

Some renders are plain GET URLs you can drop straight into an <img> or <meta> tag — an Open Graph image, or a screenshot embed. Because the URL is visible, you never put your secret key in it. Instead you sign it with your public key: append pk=<your public key> and sig=<signature>, where the signature is an HMAC-SHA256 of the request path plus the query string — sorted, with sig removed — using your signing secret (shown in the portal). The public key and signing secret are safe to ship; the secret key stays server-side.

<!-- an Open Graph image, embedded in your <head> -->
<meta property="og:image"
  content="https://renderhelm.keelhelm.com/v1/og/blog-header.png?title=Hello&pk=pk_you&sig=…">

# a signed screenshot embed (same signing scheme)
GET /v1/screenshot?url=https://example.com&pk=pk_you&sig=…

Portal

Your account portal manages everything without touching the API: sign in with a one-time email link — no password — then create and label up to five keys, roll a key to reveal a fresh secret if you've lost one, watch your monthly usage, and run any endpoint from the built-in playground. The playground meters against your plan exactly like a real call, and your secret key never leaves the server.

Cache

On a paid plan, every render is stored in your account's private cache. Ask for the same render again and it's served from cache in milliseconds for free — a cache hit costs zero units. Cached items live 6 months and the clock resets on every hit, so what you reuse stays warm. When your cache is full the least-recently-used items are evicted to make room; if only pinned items remain, the new render simply isn't cached. The free plan has no cache — every free render is fresh and counted.

Two flags on any render request control caching, and a header tells you the hash to manage:

FieldEffect
freshSkip the cache lookup and re-render — but still store the result.
no_cacheDon't read or write the cache at all: a fresh, billed render that isn't stored.
response: "url"Return a JSON link to the cached object instead of the bytes (paid plans only).
X-Render-HashResponse header — the hash you pin or delete below.
GET /v1/cache

Manage your cache from the portal or the API (Bearer sk_).

RouteDoes
GET /v1/cacheList cached renders + usage (bytes used / limit, item count).
DELETE /v1/cache/:hashDelete one cached render.
POST /v1/cache/:hash/pin · /unpinPin (never evict or expire) or unpin an entry.

Limits & units

A screenshot or Open Graph image is one render; a full-page capture, a PDF, or a Compose render to PDF is two; a cache hit is zero. Plans set a monthly render budget plus a per-minute rate — see pricing. Go over your plan and, unless you've switched on metered overage ($1.50 per 1,000 renders, off by default), new renders pause until your next cycle. On paid plans, identical requests are served from your cache and never counted (a cached item is re-billed only after 6 months of no use, or if you evict it); the free plan has no cache, so every free render counts.

Errors

Every error returns JSON: {"error":{"code","message","doc_url"}} with the right HTTP status.

CodeStatusMeaning
unauthorized401Missing or invalid key.
bad_signature401Open Graph signature didn't match the parameters.
blocked_target400The target URL isn't allowed (private/reserved address).
rate_limited429Too many requests this minute — slow down.
quota_exceeded429Monthly render budget reached.
payload_too_large413Inline HTML exceeds 2 MB.
template_syntax_error400A /v1/render template couldn't be parsed (with an offset).
template_limits_exceeded400A template exceeded a size, nesting, loop, or output limit.
tier_required403Private CSS needs a Pro or Scale plan.
css_not_found404The referenced css_id doesn't exist for your account.
css_limit_reached409You already have 20 stored stylesheets.
demo_requires_key400The demo only renders its ready-made examples — get a free key for your own.
render_failed500The page couldn't be rendered — retry.