Guide · 2026-10-10
Images from Node.js
fetch, a template id, and a webhook so a thousand renders do not block a request.
A single render
const API = "https://api.imageapis.com";
const headers = { "X-API-Key": process.env.IMAGEAPIS_KEY, "Content-Type": "application/json" };
const r = await fetch(`${API}/v1/images`, {
method: "POST", headers,
body: JSON.stringify({ template: "tpl_4nq8vzk2hdyw7bxme3rc", variables: { title: "Hello from Node" }, format: "webp" }),
});
const image = await r.json();
console.log(image.url, r.headers.get("x-gw-remaining"));
Many renders: webhooks
Each render takes a second or two in a real browser. For a queue of them, ask for a webhook and move on:
for (const post of posts) {
await fetch(`${API}/v1/images`, {
method: "POST", headers,
body: JSON.stringify({
template: "tpl_4nq8vzk2hdyw7bxme3rc",
variables: { title: post.title },
webhook_url: "https://example.com/hooks/imageapis",
metadata: { postId: post.id }, // comes back with the record
}),
}); // 202 { id, status: "pending", url }
}
The url is known up front, so you can store it immediately; it starts answering when the render completes.
Receiving the webhook
import express from "express";
const app = express();
app.post("/hooks/imageapis", express.json(), async (req, res) => {
const { event, image } = req.body; // event: render.completed | render.failed
if (event === "render.completed") await db.posts.update(image.metadata.postId, { ogImage: image.url });
else console.error("render failed", image.id, image.error);
res.sendStatus(204);
});
The request carries an X-ImageAPIs-Event header with the same event name. Webhook URLs must be public https addresses; private and local hosts are refused when you submit the job.
Checking on a job
GET /v1/images/{id} returns the record with status pending, completed or failed, and costs nothing.