Signed URLs
Put the URL in a page before the image exists. The first fetch renders it, the edge serves the rest.
The shape
https://img.imageapis.com/s/<base>/<template>.png?title=Hello&site=example.com&exp=1760000000&sig=<hex>
<base>is a signed base you create withPOST /v1/signed-bases. It carries a secret and is tied to the key that made it.<template>is a template id, orogfor the built-in Open Graph card.- Every query parameter except
sigandexpbecomes a variable for the template (or an option for theogcard). expis optional: a Unix timestamp after which the URL stops working.sigishex(HMAC-SHA256(secret, path + "?" + query)), wherequeryis the query string exactly as you send it, withoutsig.
.jpg and .webp work in place of .png.
Signing
Python:
import hmac, hashlib
msg = f"/s/{base}/og.png?title=Hello&site=example.com"
sig = hmac.new(secret.encode(), msg.encode(), hashlib.sha256).hexdigest()
url = f"https://img.imageapis.com{msg}&sig={sig}"
Node:
import { createHmac } from "node:crypto";
const msg = `/s/${base}/og.png?${new URLSearchParams({ title: "Hello", site: "example.com" })}`;
const sig = createHmac("sha256", secret).update(msg).digest("hex");
const url = `https://img.imageapis.com${msg}&sig=${sig}`;
PHP:
$msg = "/s/$base/og.png?" . http_build_query(["title" => "Hello", "site" => "example.com"]);
$url = "https://img.imageapis.com$msg&sig=" . hash_hmac("sha256", $msg, $secret);
Sign the exact string you will send. If your URL builder encodes spaces as %20 and your signer used +, the signature will not match; the error response shows the string the server signed so you can compare.
What a fetch costs
The first fetch of a URL renders it: one image against the plan of the key that created the base. The result is cached at Cloudflare's edge for a day and in storage for a week, so the same URL fetched by every crawler and reader costs nothing more. Change the template and the next fetch re-renders, because the template's content is part of the cache key.
If the plan is out of images, the URL answers 429 with the plan's numbers instead of an image, and resumes at the reset.
Locking and revoking
Create a base with "template": "tpl_…" or "template": "og" and it will render only that. Delete the base and every URL built on it stops. Rotating the API key that created the base stops it too, since renders are charged to that key.
Where to use them
og:image tags, email templates, README badges, dashboards, anywhere you want an image that reflects data at the time someone looks, without a render step in your deploy.