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.

Get a free key: 150 images a month API reference