Open Graph images & certificates, rendered from a URL.
Upload an HTML template as JSON once. Every combination of parameters becomes an immutable, edge-cacheable webp — social cards, changelog banners, course certificates — no image pipeline to run.
GET https://<tenant>.media0.subprocess.io/image/certificate?recipient=Ada+Lovelace&course=Analytical+Engines+101 302 -> /image/t/{templateID}/{version}/webp?course=…&recipient=… 200 image/webp · Cache-Control: public, max-age=86400, immutable
How it works
A template is a single JSON document: Go html/template markup plus declared parameters. Everything downstream is boring, cacheable HTTP.
POST a template
One JSON spec: HTML, width/height, and typed params (string, number, image_url). Validate it for free first.
Versions are immutable
Every publish mints an ascending version (000001, 000002, …). Old URLs keep rendering exactly what they always did.
Aliases 302 to latest
Point /image/certificate at a template. Consumers hit the alias; it redirects to the fully-qualified, immutable render URL.
Rendered once, cached hard
Headless Chromium renders on the first request; the webp is cached (in memory and central blob storage) with a 24-hour TTL that every access extends. Immutable URLs mean any CDN in front caches them as an ordinary proxy.
Built agent-first
The whole lifecycle is three HTTP calls with JSON in and JSON out —
no dashboard clicks required. AI agents design a spec, dry-run it
against /templates/validate, publish, and hand back a URL.
The agent guide ships a drop-in system prompt.
Even signup is agent-first: no email verification, no CAPTCHA — a
Hashcash-style proof of work (a few seconds of CPU) buys a tenant,
a write API key and ten recovery codes in one call. The response
includes a claim_url: the agent hands it to its human
together with one recovery code, and the human redeems the code in
the console to take over — registering passkeys and an email.
# 0. sign up as an agent: solve a proof of work, get an account curl -s -X POST https://media0.subprocess.io/auth/pow/challenge # -> {"challenge": "<64 hex>", "bits": 22, "expires_at": "…"} # brute-force an ASCII nonce until sha256(challenge + ":" + nonce) # starts with >= bits leading zero bits: n=0; while true; do h=$(printf '%s:%s' "$CHALLENGE" "$n" | sha256sum | cut -c1-6) [ "$h" = "000000" ] && break # 24 bits; server needs 22 n=$((n+1)) done curl -s https://media0.subprocess.io/auth/agent/signup \ -d "{\"challenge\": \"$CHALLENGE\", \"nonce\": \"$n\", \"name\": \"my-bot\"}" # -> 201 {"tenant_id": …, "api_key": {"secret": "m0_…", "scope": "write"}, # "recovery_codes": [10 one-time codes], "claim_url": "…/app/#/claim?tenant=…"}
# 1. dry-run: parse + normalize, costs nothing curl -s https://media0.subprocess.io/api/v1/templates/validate \ -H "Authorization: Bearer $M0_KEY" \ -d '{"spec": {"html": "<h1 class=\"og-title\">{{.title}}</h1>", "params": {"title": {"required": true}}}}' # 2. publish curl -s -X POST https://media0.subprocess.io/api/v1/templates \ -H "Authorization: Bearer $M0_KEY" -d @spec.json # 3. name it curl -s -X PUT https://media0.subprocess.io/api/v1/aliases/launch \ -H "Authorization: Bearer $M0_KEY" \ -d '{"template_id": "<id from step 2>"}'
Why agents like it
Specs are self-describing: every parameter carries a type, default and description, so a model can read a template back and know how to call it. Validation returns machine-readable problems, never a rendered bill.
The render sandbox is deterministic by construction: no external
network, a shared /lib asset library (CSS utilities,
SVG backdrops, laurels and seals for certificates), and
image_url params fetched server-side with SSRF guards.
Read keys can list, inspect and validate; write keys publish. Give your agent the scope it needs and nothing more.
Simple limits
Every tenant gets the same generous defaults. Renders are only counted on cache misses — cached traffic is free and unmetered.
| What | Limit | Notes |
|---|---|---|
| Templates | 50 per tenant | unlimited immutable versions each |
| Renders | 10,000 / month | cache-miss renders only; hits are free |
| Render budget | 1 second | per render, headless Chromium |
| Image cache | 24 h sliding | every access extends the TTL |
| Output | webp | default 1200×630 @ 2× scale, up to 4096 px |
Docs
Reference material ships with the repository:
docs/API.md (every endpoint with curl examples),
docs/AGENT_GUIDE.md (template authoring for AI agents),
docs/ARCHITECTURE.md (how the cache hierarchy works) and
ready-to-POST specs under examples/.