Get an API Key Demo

Batch Social Cards

Generate dozens of branded image cards in a single API call — all rendered in parallel.

The problem

You publish 20 news articles at midnight. Each one needs a 1080×1080 social card for Instagram, a 1200×630 card for Twitter, and a vertical 1080×1350 for Stories. Calling the API one by one means sequential HTTP round-trips — slow and wasteful.

With POST /v1/render/batch you send all requests in a single call. StarkRender renders them in parallel and returns all results when the last one completes — total time ≈ the slowest single render, not the sum.


Step 1 — Create the template

Design your card once in HTML with {{variable}} placeholders. Save it with POST /v1/template. You need to do this only once — reuse the same template_id for every batch call.

HTML Template (1080×1080)
<div style="
  width:1080px; height:1080px;
  background:#0f172a;
  display:flex; flex-direction:column;
  justify-content:center; align-items:flex-start;
  padding:80px;
  font-family:Inter, sans-serif;
  box-sizing:border-box;
  position:relative;
">
  <span style="
    background:#C9952A; color:#000;
    font-size:18px; font-weight:700;
    padding:6px 16px; border-radius:4px;
    margin-bottom:32px; display:inline-block;
  ">{{category}}</span>

  <h1 style="
    color:#fff; font-size:58px; font-weight:800;
    line-height:1.2; margin:0 0 40px;
    display:-webkit-box; -webkit-line-clamp:4;
    -webkit-box-orient:vertical; overflow:hidden;
  ">{{headline}}</h1>

  <p style="color:#94a3b8; font-size:24px; margin:0">{{source}} · {{date}}</p>

  <div style="position:absolute;bottom:48px;right:60px;color:#C9952A;font-size:20px;font-weight:700">
    YourBrand
  </div>
</div>
cURL — Save template
curl -X POST https://api.starkrender.com/v1/template \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "News card 1080", "html": "<!-- template above -->"}'
Response
{
  "status":      "success",
  "template_id": "86c4dab7-6570-442a-9a46-74bb2a472aed"
}

Step 2 — Build the batch request

Each item in requests accepts the same parameters as POST /v1/render. Mix different sizes, formats, or even different templates in the same batch.

Node.js

JavaScript
const TEMPLATE_ID = '86c4dab7-6570-442a-9a46-74bb2a472aed';

async function batchRenderCards(articles) {
  // Split into chunks of 25 (API limit per call)
  const chunks = chunkArray(articles, 25);
  const allResults = [];

  for (const chunk of chunks) {
    const res = await fetch('https://api.starkrender.com/v1/render/batch', {
      method: 'POST',
      headers: {
        'x-api-key':    process.env.STARKRENDER_API_KEY,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        requests: chunk.map(article => ({
          template_id:  TEMPLATE_ID,
          variables: {
            headline:  article.title,
            category:  article.category,
            source:    article.source,
            date:      article.publishedAt,
          },
          width:        1080,
          height:       1080,
          device_scale: 2,
        })),
      }),
    });

    const { results } = await res.json();
    allResults.push(...results);
  }

  return allResults;   // results[i] matches articles[i]
}

function chunkArray(arr, size) {
  const out = [];
  for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size));
  return out;
}

Python

Python
import os, requests
from itertools import islice

TEMPLATE_ID = "86c4dab7-6570-442a-9a46-74bb2a472aed"
API_KEY     = os.environ["STARKRENDER_API_KEY"]
HEADERS     = {"x-api-key": API_KEY, "Content-Type": "application/json"}

def chunk(lst, size):
    it = iter(lst)
    while batch := list(islice(it, size)):
        yield batch

def batch_render_cards(articles: list[dict]) -> list[dict]:
    results = []
    for chunk_articles in chunk(articles, 25):
        resp = requests.post(
            "https://api.starkrender.com/v1/render/batch",
            headers=HEADERS,
            json={
                "requests": [
                    {
                        "template_id": TEMPLATE_ID,
                        "variables": {
                            "headline":  a["title"],
                            "category":  a["category"],
                            "source":    a["source"],
                            "date":      a["published_at"],
                        },
                        "width":        1080,
                        "height":       1080,
                        "device_scale": 2,
                    }
                    for a in chunk_articles
                ]
            },
        )
        resp.raise_for_status()
        results.extend(resp.json()["results"])
    return results   # results[i] matches articles[i]

Step 3 — Handle the results

The results array is returned in the same order as the input. Each item is either a success with url and id, or a failure with error and statusCode. One failed render does not affect the others.

Response
{
  "results": [
    { "url": "https://api.starkrender.com/v1/image/uuid-1", "id": "uuid-1" },
    { "url": "https://api.starkrender.com/v1/image/uuid-2", "id": "uuid-2" },
    { "error": "html or template_id is required", "statusCode": 400 }
  ]
}
JavaScript — process results
const results = await batchRenderCards(articles);

const saved = [];
const failed = [];

results.forEach((result, i) => {
  if (result.error) {
    failed.push({ article: articles[i], reason: result.error });
  } else {
    saved.push({ article: articles[i], imageUrl: result.url });
  }
});

console.log(`Done: ${saved.length} rendered, ${failed.length} failed`);

Tips

Check quota before a large run

The batch endpoint deducts N renders upfront. If you don't have enough quota, the entire call returns 429 before any rendering starts. Call GET /v1/usage first when rendering large volumes.

JavaScript
const usage = await fetch('https://api.starkrender.com/v1/usage', {
  headers: { 'x-api-key': API_KEY },
}).then(r => r.json());

if (usage.renders.remaining < articles.length) {
  throw new Error(`Only ${usage.renders.remaining} renders left, need ${articles.length}`);
}

Multiple formats in one batch

You can mix different sizes in the same request. For example, generate a square card and a landscape card for each article at once:

JSON Body
{
  "requests": [
    // Article A — square (Instagram)
    { "template_id": "...", "variables": { "headline": "Article A", ... }, "width": 1080, "height": 1080 },
    // Article A — landscape (Twitter)
    { "template_id": "...", "variables": { "headline": "Article A", ... }, "width": 1200, "height": 630 },
    // Article B — square
    { "template_id": "...", "variables": { "headline": "Article B", ... }, "width": 1080, "height": 1080 }
  ]
}

More than 25 items

The limit is 25 items per call. For larger sets, split into chunks of 25 and call the endpoint sequentially — as shown in the code examples above. Each chunk is still rendered in parallel internally, so the total time scales as ceil(N / 25) × single-render time.

On This Page
The Problem Step 1 — Create the template Step 2 — Build the request Step 3 — Handle results Tips