Starhold Render

An API that screenshots any public page

Send a URL or HTML, get an image. Choose the viewport, full page or not, dark mode, and whether to hide cookie banners.

Get a free key Full reference

Request and response

Send JSON with your key in the Authorization header. A good request returns the file itself with a Content-Type, an X-Render-Usage header such as 12/1000, and X-Render-Cache: hit or miss. A bad one returns JSON with a code, a plain sentence and a link to the docs.

curl
curl -X POST https://render.starhold.app/v1/screenshot \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","viewport":{"width":1280,"height":800},"format":"png"}' \
  -o shot.png
JavaScript (fetch)
import { writeFile } from "node:fs/promises";

const res = await fetch("https://render.starhold.app/v1/screenshot", {
  method: "POST",
  headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json" },
  body: '{"url":"https://example.com","viewport":{"width":1280,"height":800},"format":"png"}',
});
if (!res.ok) throw new Error((await res.json()).error.message);
await writeFile("shot.png", Buffer.from(await res.arrayBuffer()));
Python (requests)
import requests

body = '{"url":"https://example.com","viewport":{"width":1280,"height":800},"format":"png"}'
res = requests.post("https://render.starhold.app/v1/screenshot",
    headers={"Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json"},
    data=body.encode(), timeout=35)
res.raise_for_status()
open("shot.png", "wb").write(res.content)
PHP (curl ext)
<?php
$body = '{"url":"https://example.com","viewport":{"width":1280,"height":800},"format":"png"}';
$ch = curl_init("https://render.starhold.app/v1/screenshot");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $body,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer YOUR_API_KEY", "Content-Type: application/json"],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 35,
]);
$out = curl_exec($ch);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) !== 200) { die($out); }
file_put_contents("shot.png", $out);
Go (net/http)
package main

import (
	"io"
	"net/http"
	"os"
	"strings"
	"time"
)

func main() {
	body := `{"url":"https://example.com","viewport":{"width":1280,"height":800},"format":"png"}`
	req, _ := http.NewRequest("POST", "https://render.starhold.app/v1/screenshot", strings.NewReader(body))
	req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
	req.Header.Set("Content-Type", "application/json")
	res, err := (&http.Client{Timeout: 35 * time.Second}).Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		io.Copy(os.Stderr, res.Body)
		os.Exit(1)
	}
	out, _ := os.Create("shot.png")
	io.Copy(out, res.Body)
}

Main options

FieldWhat it does
url or htmlexactly one
viewport, device_scale_factor320 to 3840 by 240 to 2160; scale 1, 2 or 3 (width times scale up to 7,680)
full_pagecaptures up to 16,384 CSS px tall; longer pages are cut and flagged with X-Render-Truncated: true
format, qualitypng, jpeg or webp; quality 1 to 100
dark_mode, block_cookie_banners, block_ads, hide_selectorspage-level switches; up to 20 selectors to hide

Pricing in short

Free is 50 renders a month. Hobby is $5 for 1,000, Pro is $19 for 10,000 and Scale is $49 for 50,000. Cache hits and failed renders do not count. Full pricing

Questions

Do you follow redirects?

Yes, up to 5, and each hop goes through the same guard, so a redirect to a private address is refused.

What if the page is slow?

A render has 20 seconds. After that you get render_timeout, and nothing is counted against your allowance.

What does the Free plan add?

A small watermark in the bottom-right corner. Paid plans never have one.