Guide · 2026-10-10

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 with POST /v1/signed-bases. It carries a secret and is tied to the key that made it.
  • <template> is a template id, or og for the built-in Open Graph card.
  • Every query parameter except sig and exp becomes a variable for the template (or an option for the og card).
  • exp is optional: a Unix timestamp after which the URL stops working.
  • sig is hex(HMAC-SHA256(secret, path + "?" + query)), where query is the query string exactly as you send it, without sig.

.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.

Get a free key: 150 images a month API reference