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.
<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 -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 -->"}'
{
"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
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
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.
{
"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 }
]
}
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.
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:
{
"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.
StarkRender