API Reference

Documentation

Everything you need to render HTML to pixels.

Authentication

All /render endpoints require a Bearer token:

curl -X POST https://www.supaserv.io/render \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html": "<h1>Hello</h1>"}'

Keys are issued per client and held as environment variables on the gateway. There is no sign-up and no dashboard. Each request is logged with the name of the key it came in with, and a key is withdrawn by deleting that one variable. Keep it secret, it authenticates every request.

POST /render

Render HTML to a PNG screenshot.

Request Body

FieldTypeDefaultDescription
htmlstring-HTML to render (required)
widthnumber1080Viewport width (100–3840)
heightnumber1350Viewport height (100–3840)
seek_msnumber-Wait ms before capture (for animations)
wait_readybooleantrueWait for window.__READY

Example

curl -X POST https://www.supaserv.io/render \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<div style=\"background:#000;color:#fff;padding:40px\">Hello World</div>",
    "width": 1080,
    "height": 1080
  }' \
  --output screenshot.png

Returns: image/png

POST /render/pdf v1 · Chromium

Render HTML to a PDF document using the Chromium engine. Also available as /render/pdfv1.

Request Body

FieldTypeDefaultDescription
htmlstring-HTML to render (required)
formatstring"A4"Page format (A4, A3, Letter, Legal)
landscapebooleanfalseLandscape orientation
marginobject-Page margins {top, right, bottom, left}
print_backgroundbooleantrueInclude background colors/images

Example

curl -X POST https://www.supaserv.io/render/pdf \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<h1>Invoice #42</h1><p>Total: EUR 99.00</p>",
    "format": "A4",
    "print_background": true
  }' \
  --output invoice.pdf

Returns: application/pdf

POST /render/pdfv2 v2 · WeasyPrint

Print-ready PDF rendering with CMYK, ICC profiles, bleed, trim marks, and PDF/X-4 support. Powered by WeasyPrint.

Full CSS Paged Media support. Falls back to Chromium if WeasyPrint is unavailable.

Full PDF v2 documentation →

Quick Example

curl -X POST https://www.supaserv.io/render/pdfv2 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<h1>Print Flyer</h1>",
    "format": "A4",
    "color_mode": "cmyk",
    "bleed": "3mm",
    "trim_marks": true
  }' \
  --output flyer-print.pdf

Returns: application/pdf

POST /v1/pdf v1 · contract

The versioned, stateless render contract. Every asset a document needs — fonts, images, the ICC profile — is passed inline as base64. The one exception is a colour profile supaserv ships itself: that one you name (icc). The renderer fetches nothing from the network. A reference it cannot resolve is an error, never a silent substitution.

Request Body

FieldTypeDefaultDescription
htmlstring-Complete document (required). Page geometry comes from @page CSS only.
profilestring"screen"screen = RGB PDF 1.7. print = PDF/X-4, DeviceCMYK, ISO Coated v2 output intent, bleed and crop marks.
assetsarray[]Objects { name, media_type, data }, data is standard base64. name must match the reference in the HTML byte for byte.
crop_marksbooleantrueprint only. Crop and registration marks outside the bleed.
iccstring-print only. Name of an ICC profile supaserv ships. Today exactly one: iso-coated-v2-300 = ISO Coated v2 300% (ECI), sha256 c6b4b62f…cdab8. Takes the place of the inline ICC asset, never together with one.
output_intentstringfrom icc, else "ISO Coated v2 300% (ECI)"print only. The /OutputConditionIdentifier written into the PDF. With icc it is that profile's identifier, and any other value is 400 unsupported. With an inline ICC asset it must describe the asset you sent; supaserv does not verify the two agree.

Fail closed: profile: "print" needs exactly one ICC profile: icc by name, or exactly one asset with media_type: "application/vnd.iccprofile". Neither, two inline, unreadable or not CMYK returns render_failed and no PDF. It never ships a file that claims PDF/X-4 without carrying the profile.

Colour is not converted. supaserv embeds the output intent, it does not transform your values. Send device-cmyk() in the CSS and CMYK images in the assets.

POST /v1/image

Same contract, same assets, same errors. One viewport per request, PNG, JPEG or WebP.

FieldTypeDefaultDescription
htmlstring-Complete document (required)
widthnumber-CSS pixels (required)
heightnumber-CSS pixels. Omit for a full-page capture.
formatstring"png"png, jpeg or webp
scalenumber1Device pixel ratio, 1–4. Output is width × scale.
transparentbooleanfalsepng and webp only
qualitynumber85jpeg and webp only
assetsarray[]As above

Errors

A closed list. Nothing else is returned.

CodeStatusTrigger
auth401Missing or unknown bearer token
unsupported400Unknown field, bad value, or a reference the renderer is not allowed to fetch (any absolute URL, any <base>). /v1/pdf: an icc name not in the list (the message lists the names that exist), icc without profile: "print", icc together with an inline ICC asset, output_intent that contradicts icc.
asset_missing400The HTML references an asset that is not in assets
too_large413Body over 4 MB at the gateway, 32 MB at the worker
rate_limited429Per key and path, per minute
render_failed500 / 504The renderer could not produce the file. 504 when a stage ran out of time.

Response headers

X-Engine, X-Engine-Version, X-Duration-Ms, X-Pages (pdf), X-Fonts-Embedded, X-ICC-SHA256 (pdf print: sha256 of the embedded profile, named or inline).

Requests over 4 MB must go to the worker directly at https://api.supaserv.io; the gateway is capped by the Vercel Edge body limit. Paths, bodies and errors are identical on both hosts.

POST /render/video

Capture animated HTML as MP4 or GIF.

Request Body

FieldTypeDefaultDescription
htmlstring-HTML to render (required)
widthnumber1080Viewport width (100–3840)
heightnumber1350Viewport height (100–3840)
fpsnumber10Frames per second (1–30)
duration_msnumber-Duration in ms (100–30000, required)
formatstring"mp4""mp4" or "gif"

Example

curl -X POST https://www.supaserv.io/render/video \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<div id=\"anim\">...</div>",
    "width": 1080,
    "height": 1920,
    "fps": 30,
    "duration_ms": 5000,
    "format": "mp4"
  }' \
  --output animation.mp4

Returns: video/mp4 or image/gif

Limit: max 900 frames total (fps × duration). Reduce FPS or duration if exceeded.

POST /render/youtube off by default

Disabled in production. The route and yt-dlp stay in the image, but the worker answers 503 {"error":"route disabled"} unless YOUTUBE_ENABLED=true is set. The same handler serves /render/twitter and /render/x, so all three are off together. Everything below describes the enabled behaviour.

Download a video as MP4 from YouTube or any of ~1000 sites supported by yt-dlp (Vimeo, TikTok, Twitter/X, Twitch, …).

Request

FieldTypeDescription
urlstringRequired. Video URL (https only; private/loopback hosts rejected).
formatstringOne of best (default), 2160p, 1440p, 1080p, 720p, 480p, 360p, audio, or a raw yt-dlp format string (e.g. 137+140).

List available formats

Add ?info=true to inspect metadata + every available stream without downloading:

curl -X POST "https://www.supaserv.io/render/youtube?info=true" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://youtu.be/oc6RV5c1yd0"}'

Returns application/json: { id, title, uploader, duration_s, thumbnail, formats: […] }

Download

curl -X POST https://www.supaserv.io/render/youtube \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://youtu.be/oc6RV5c1yd0","format":"1080p"}' \
  --output video.mp4

Returns: video/mp4 (or audio/mp4 for format=audio).

Limits & long videos

Default max duration: 30 minutes (worker-side, overridable via YTDLP_MAX_DURATION_S env).

Videos longer than 3 minutes return 503 with a worker_endpoint hint. Call the worker directly to bypass the gateway timeout:

curl -X POST https://api.supaserv.io/youtube \
  -H "Authorization: Bearer WORKER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"...","format":"best"}' \
  --output video.mp4

Note: respect each platform’s ToS and applicable copyright. Self-hosted means you own the compliance choice.

POST /render/rembg NEW

Remove background from an image. Returns a transparent PNG. Powered by rembg (u2net model) with edge refinement.

Best suited for images with white or light backgrounds (product photos, headshots, logos).

Request

Send as multipart/form-data with a file field. Tune aggressiveness with the optional X-Force header (Low / Medium / High):

FieldTypeDescription
fileFileImage file (required). JPG, PNG, or WEBP. Max 10 MB.
X-ForceheaderCut strength: Low, Medium (default), or High. Higher = more aggressive matting.

Example

curl -X POST https://www.supaserv.io/render/rembg \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@photo.jpg" \
  --output cutout.png

Returns: image/png (RGBA with transparent background)

Errors

StatusReason
400Missing file, wrong Content-Type, or unsupported image format
413File exceeds 10 MB limit
500Processing error

Performance: ~3s per image (warm). First request after deploy may take longer.

POST /render/pdf2text NEW

Extract text from a PDF, or let Claude read it and hand back structured JSON. Text is pulled with PyMuPDF; optional flags add vision OCR and document intelligence.

Request

Send as multipart/form-data with a file field. Behaviour is controlled by query flags:

FlagTypeDescription
fileFilePDF file (required). Max 20 MB.
?analyze=truebooleanDetect document type & return typed JSON (invoice, contract, letter, report, cv, form, …). Cheap, text-only.
?llm=truebooleanVision OCR fallback for scanned / image-only PDFs (no text layer).
?visual=truebooleanRender pages & run full Claude vision analysis.

Example: invoice to JSON

curl -X POST "https://www.supaserv.io/render/pdf2text?analyze=true" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@invoice.pdf"

Returns: application/json

// with ?analyze=true: detected fields live under "document"
{
  "method": "analyze",
  "summary": "...",
  "pages": 1,
  "document": {
    "type": "invoice",
    "language": "de",
    "confidence": 0.98,
    "invoice_number": "2026-0420",
    "total": 678.30,
    "currency": "EUR",
    "line_items": [ /* description, quantity, unit_price, total, vat_rate */ ],
    "tax_details": [ /* rate, base, amount */ ]
  }
}

Flags also work as headers (X-Analyze, X-Llm, X-Visual). Without any flag it returns plain extracted text + page count. Analysis uses Claude via the worker, rate limited to 10/min.

POST /render/batch

Render one animated document as a sequence of base64 PNG frames, returned as JSON instead of an encoded video. Use it when you want to do your own encoding, build a sprite sheet, or inspect single frames.

Request Body

FieldTypeDefaultDescription
htmlstring-HTML to render (required)
widthnumber1080Viewport width (100–3840)
heightnumber1350Viewport height (100–3840)
fpsnumber10Frames per second (1–30)
duration_msnumber-Duration in ms (100–30000, required)
concurrencynumber4Frames rendered in parallel (1–6)

Example

curl -X POST https://www.supaserv.io/render/batch \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html":"<div id=\"anim\">...</div>","fps":10,"duration_ms":3000}'

Returns: application/json

{
  "frames": [ /* base64 PNG, one per frame */ ],
  "fps": 10,
  "duration_ms": 3000,
  "width": 1080,
  "height": 1350,
  "total_frames": 30
}

Limit: max 300 frames (fps × duration). The response is JSON, so it is far larger than the equivalent MP4 — use /render/video unless you need the individual frames.

GET /health

Health check endpoint. No authentication required.

curl https://www.supaserv.io/health
// Response
{
  "status": "ok",
  "service": "supaserv",
  "version": "0.2.0",
  "backend": "worker"
}