Bannerbear vs Placid: Template Image APIs Compared

Bannerbear vs Placid for generated images: API shape, sync vs async, signed image URLs, credit rules, prices at 1k to 100k images, and a script that tests both.

By Simon Chiu · Updated

Disclosure: I build RenderScreenshot, which can also generate social images from HTML templates. That makes both products indirect competitors. I have not rendered with either service for this page: the comparison below is built from their API references and pricing pages (read on 2026-10-15), and the test script is untested against live accounts. I note where each one is the better choice.

Short answer: both are template engines with an API. You design a template in their editor, then send text and images to fill named layers. Choose Placid if you want simple credit maths (one image, one credit), a URL API you can drop into og:image tags, a synchronous create_now option, and a template layer that can embed a live website screenshot. Choose Bannerbear if you also need animations, multi-step workflows, AI image tools or an MCP server for agents, and you are comfortable with credits that multiply by format and scale.

Neither is a screenshot API. If what you need is a picture of a web page, not a designed card, see the screenshot tool comparisons instead.

How each API is shaped

Bannerbear Placid
Base API https://api.bannerbear.com/v5 https://api.placid.app/api/rest
Auth Authorization: Bearer with a V5 key; keys can be scoped (for example images:write) and restricted to browser origins Authorization: Bearer with a project-specific token
Create an image POST /v5/images with template and modifications.objects[] POST /images with template_uuid and a layers object
Default flow Async: 202 Accepted, then poll GET /v5/images/:uid or receive a webhook Async: status: "queued", then poll polling_url or receive webhook_success
Synchronous option Same request to https://sync.api.bannerbear.com; 10-second limit, 408 on timeout "create_now": true in the body; can fail when your worker is busy
URL-based images (for og:image) Instant URLs, HMAC signed, bound to one template; listed on Scale and above URL API: https://api.placid.app/u/{template_uuid}?layer[text]=..., optional HMAC signature in &s=
Output formats JPG, PNG, WebP, AVIF, PDF, set per request in formats[] auto (JPG or PNG), JPG, PNG, WebP via modifications.image_format
Resolution scale 1 to 4 Width and height modifications, DPI for print
Storage Hosted file URLs; custom S3 on Scale and above Hosted URL; transfer to your S3 bucket in the request
Website capture v2 POST /v2/screenshots (url, width, height, mobile, language) Browserframe layer: image can be a website URL, image_viewport defaults to 1280x1024
Beyond images Animations (MP4), workflows, AI tools, MCP server PDFs, videos, Editor SDK, MCP
Rate limit (documented) 60 POST requests per 10 seconds See Placid's rate-limit page

Sources: Bannerbear API reference, Bannerbear v2 reference, Placid image API, Placid layers, Placid URL API.

Two details stand out when you write the integration:

  • Bannerbear v5 keys do not work on v2 endpoints, and the reverse. If you depend on the v2 screenshot endpoint, you keep a v2 key for it.
  • Placid's create_now is not a guarantee. Its docs say it may fail when too many requests run at once. Keep the polling path as a fallback, as the script below does.

Credits: the rule that changes your bill

Bannerbear: one image render is one credit, multiplied by the number of formats and by the scale. Ask for JPG and PNG at scale 2, and one render costs four credits. AI features (background removal, subject detection) add one credit per layer that uses them.

Placid: one image is one credit for canvases up to 4,000 px. A PDF page is two credits, and video costs one credit per second. Previews in test mode are free and watermarked.

So the first habit with Bannerbear is to request exactly one format at the scale you need. If you need a PNG for one platform and a JPG for another, compare the cost of two formats against converting one file yourself.

Price at three volumes

One output format, scale 1, list prices as shown on each pricing page on 2026-10-15:

Images per month Bannerbear plan Placid plan
1,000 Automate, $49/mo (1,000 API credits) Pro (2,500 credits/mo)
10,000 Scale, $149/mo (10,000 API credits) Business (25,000 credits/mo)
100,000 Enterprise, from $299/mo for 50,000 credits; 100,000 is a higher tier on the same plan VIP (100,000 credits/mo)

Placid's page renders its dollar prices with JavaScript, and our text capture did not include them, so check Placid's pricing page for the current numbers. Bannerbear prices are from its pricing page; it offers a trial with 30 API credits.

A script to test both on your template

The fair way to compare is to build the same template in both editors (same size, same fonts, layers named title and image), then render the same inputs. Use three inputs: a short title, a long title that must wrap or shrink, and a title with emoji and Japanese text.

This Node 22 script renders one input on each service, prefers the synchronous path, falls back to polling, downloads the result, and prints timing and size. It has not been run against live accounts for this page.

// Render the same data with Bannerbear and Placid, then download and time each result.
// Env: BANNERBEAR_API_KEY, BANNERBEAR_TEMPLATE, PLACID_TOKEN, PLACID_TEMPLATE
import { writeFile } from 'node:fs/promises';

const input = {
  title: process.argv[2] ?? 'Quarterly report: what changed in October',
  image: process.argv[3] ?? 'https://example.com/cover.jpg',
};
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function json(res) {
  const body = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${JSON.stringify(body).slice(0, 300)}`);
  return body;
}

async function poll(url, headers, isDone, getUrl) {
  for (let i = 0; i < 30; i++) {
    const body = await json(await fetch(url, { headers, signal: AbortSignal.timeout(15_000) }));
    if (isDone(body)) return getUrl(body);
    if (body.status === 'failed' || body.status === 'error') throw new Error(`render failed: ${JSON.stringify(body).slice(0, 300)}`);
    await sleep(1000);
  }
  throw new Error('render did not finish in 30 s');
}

async function bannerbear() {
  const headers = { Authorization: `Bearer ${process.env.BANNERBEAR_API_KEY}`, 'Content-Type': 'application/json' };
  const body = JSON.stringify({
    template: process.env.BANNERBEAR_TEMPLATE,
    formats: ['png'], // each extra format or scale step multiplies the credit cost
    modifications: { objects: [
      { name: 'title', text: input.title },
      { name: 'image', 'background-image': input.image },
    ] },
  });
  let res = await fetch('https://sync.api.bannerbear.com/v5/images', { method: 'POST', headers, body, signal: AbortSignal.timeout(15_000) });
  // A 408 means the sync host gave up waiting; retry on the async host (check usage: the first attempt may still count).
  if (res.status === 408) res = await fetch('https://api.bannerbear.com/v5/images', { method: 'POST', headers, body });
  const image = await json(res);
  if (image.status === 'completed') return image.files.png;
  return poll(image.self, headers, (b) => b.status === 'completed', (b) => b.files.png);
}

async function placid() {
  const headers = { Authorization: `Bearer ${process.env.PLACID_TOKEN}`, 'Content-Type': 'application/json' };
  const image = await json(await fetch('https://api.placid.app/api/rest/images', {
    method: 'POST',
    headers,
    signal: AbortSignal.timeout(30_000),
    body: JSON.stringify({
      template_uuid: process.env.PLACID_TEMPLATE,
      create_now: true, // may still come back queued when busy
      layers: { title: { text: input.title }, image: { image: input.image } },
      modifications: { image_format: 'png' },
    }),
  }));
  if (image.status === 'finished') return image.image_url;
  return poll(image.polling_url, headers, (b) => b.status === 'finished', (b) => b.image_url);
}

for (const [name, render] of Object.entries({ bannerbear, placid })) {
  const t0 = performance.now();
  try {
    const url = await render();
    const t1 = performance.now();
    const file = Buffer.from(await (await fetch(url)).arrayBuffer());
    await writeFile(`${name}.png`, file);
    console.log(`${name}: render ${(t1 - t0).toFixed(0)} ms, download ${(performance.now() - t1).toFixed(0)} ms, ${file.length} bytes`);
  } catch (err) {
    console.error(`${name}: ${err.message}`);
    process.exitCode = 1;
  }
}

Run it with each of your three inputs, at least five times each, at different times of day. Report:

  • Render time, from request to a finished file URL. Async queues make the spread more useful than the median.
  • Download time and bytes of the result, since both hand you a hosted file.
  • Text fitting: does the long title wrap, shrink or overflow? Does the CJK and emoji title render, or show boxes? This depends on the fonts you uploaded, so use the same font files in both.
  • Failures: count 408s from Bannerbear's sync host and queued responses from Placid's create_now.

We will add measured results to this page when we have run the script on matched templates.

Where each one wins

Placid is the better fit when:

  • You want og:image URLs that render on first request, signed with HMAC.
  • You want to know the cost of a render before you send it.
  • You need PDFs and videos from the same templates, or an embeddable template editor for your own users (Editor SDK).
  • You want a website screenshot placed inside a designed frame.

Bannerbear is the better fit when:

  • You need animations, or multi-step workflows that chain renders and tools.
  • You want AI features such as background removal inside the render.
  • Your team works with agents through MCP, and a hosted MCP server matters.
  • You need fine control over API key scopes and allowed browser origins.

Neither is the right tool when your source is a web page or HTML you already have. A template editor duplicates your design work; rendering your own HTML keeps one source of truth.

The HTML route: render your own template

If your cards are built from your site's own design system, write the template as HTML and render it with a headless browser. With RenderScreenshot, the og_card preset sets a 1200x630 viewport, and the HTML input takes the markup directly:

import { writeFile } from 'node:fs/promises';
import { Client, TakeOptions } from 'renderscreenshot';

const escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]);
const title = escapeHtml(process.argv[2] ?? 'Quarterly report: what changed in October');

const html = `<!doctype html><html><body style="margin:0;width:1200px;height:630px;display:flex;align-items:center;
  padding:0 80px;box-sizing:border-box;background:#0f172a;color:#fff;font:700 64px/1.1 system-ui,sans-serif">${title}</body></html>`;

const client = new Client(process.env.RS_API_KEY);
await writeFile('card.png', await client.take(TakeOptions.html(html).preset('og_card')));

The escape step matters: the title is user data, and without it a title like <img src=x onerror=...> becomes markup in your template. For a signed URL you can put straight into a meta tag, use client.generateUrl() with an expiry, as described in signed URLs. Our OG image API page covers the full workflow, and you can get a free API key to try it.

Last verified: 2026-10-15 (API references and pricing pages). Measured results pending.

Frequently asked questions

What is the main difference between Bannerbear and Placid?
Both turn a designed template plus data into images. Bannerbear's API centres on a v5 REST API with image, animation, tool and workflow endpoints and an MCP server. Placid offers a REST API and a URL API with signed URLs, and its credit rule is simpler: one image is one credit.
Can Bannerbear or Placid take website screenshots?
Placid has a browserframe layer whose image can be the URL of a website to screenshot, placed inside a template. Bannerbear's older v2 API has a screenshots endpoint; its v5 reference does not list one. Neither is a full screenshot API with waits, blocking or full-page options.
Why did my Bannerbear render use four credits?
Bannerbear multiplies the credit cost by the number of formats and by the scale. One JPG at scale 1 is one credit; JPG and PNG at scale 2 is four.
Can I put a Placid or Bannerbear image URL in an og:image tag?
Yes. Placid's URL API builds images from URL parameters and supports HMAC-signed URLs. Bannerbear's Instant URLs do the same with a signing key, and are listed on its Scale plan and above.

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.