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.

Sign in 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://stage-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://stage-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://stage-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://stage-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://stage-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.