QRQR Code Agency
API reference

POST /api/v1/generate/bulk/

Render up to 5 000 QRs in a single call and receive a ZIP archive plus a manifest.json describing every item.

POST /api/v1/generate/bulk/

Render up to plan.max_bulk_items QRs in a single call. Returns a ZIP archive (Content-Type: application/zip) containing one image per item plus a manifest.json.

Free plan is locked out (403). Starter caps 50, Pro 1 000, Agency and Enterprise 5 000.

Endpoint

POST /api/v1/generate/bulk/
Host: api.qrstudio.agency
X-Api-Key: smk_...
Content-Type: application/json

Body

{
 "items": [
 { "data": "https://example.com/1", "size_inches": 3 },
 { "data": "https://example.com/2", "size_inches": 3 },
 {
 "data_type": "vcard",
 "payload": { "name": "Alice", "phone": "+15145550001" },
 "size_inches": 2
 },
 {
 "data_type": "dynamic",
 "payload": {
 "name": "Promo card 003",
 "destination_url": "https://example.com/promo/003"
 },
 "size_inches": 4
 }
 ]
}
FieldTypeRequiredDescription
itemsarray of GenerateRequestyesEach item has the same schema as POST /generate/. Min 1, max plan.max_bulk_items.

info: Logos in bulk Bulk only accepts logo_url and bg_image_url. Multipart in batch mode is intentionally unsupported. Host assets on a CDN.

Response

200 OK

HTTP/1.1 200 OK
Content-Type: application/zip
Content-Disposition: attachment; filename="qrstudio-bulk-50.zip"
X-QR-Plan: starter
X-QR-Quota-Remaining: 437
X-Request-Id: 01HZ8X...

<binary ZIP bytes>

The ZIP contains:

FileContent
qr-0001.png (or .svg)First item's render
qr-0002.pngSecond item's render
......
qr-NNNN.pngNth item's render
manifest.jsonIndex map (stored uncompressed for zero-dep readers)

manifest.json shape

{
 "count": 50,
 "plan": "starter",
 "items": [
 {
 "index": 0,
 "filename": "qr-0001.png",
 "data_type": "url",
 "format": "png",
 "size_inches": 3,
 "dpi": 300,
 "bytes": 18432,
 "cache": "MISS",
 "duration_ms": 312,
 "has_logo": false
 },
 {
 "index": 3,
 "filename": "qr-0004.png",
 "data_type": "dynamic",
 "format": "png",
 "size_inches": 4,
 "dpi": 300,
 "bytes": 22871,
 "cache": "MISS",
 "duration_ms": 287,
 "has_logo": false,
 "dynamic": {
 "short_id": "aBc12dEf",
 "public_url": "https://q.qrstudio.agency/q/aBc12dEf/"
 }
 }
 ]
}
Manifest fieldTypeDescription
countintNumber of rendered items
planstringThe plan code that rendered the batch
items[].indexint0-based position in the original items[] request
items[].filenamestringFilename inside the ZIP
items[].data_typestringurl, vcard, dynamic, etc.
items[].formatstringpng or svg
items[].bytesintSize of the image in bytes
items[].cachestringHIT or MISS
items[].duration_msintRender time, 0 on cache hit
items[].has_logoboolWhether a logo was composited
items[].dynamic.short_idstringPresent when data_type: "dynamic"
items[].dynamic.public_urlstringPresent when data_type: "dynamic"

400 Bad Request

Plan does not include bulk, or items count exceeds plan cap, or output ZIP exceeded the 100 MB ceiling.

{
 "detail": "Plan 'free' doesn't include bulk generation. Upgrade to Starter or higher."
}
{
 "items": "Plan 'starter' caps bulk at 50 items per request, got 200."
}

422 Unprocessable Entity

Per-item validation failure. The whole batch is rejected; we never emit a partial ZIP.

{
 "errors": {
 "3": "size_inches > plan max (8)",
 "17": "Invalid color 'rouge'. Use 'black', 'white', or hex like #RRGGBB."
 },
 "rendered_before_failure": 17
}

rendered_before_failure indicates how many items had successfully rendered before the first per-item failure stopped the batch. Your quota is not consumed.

429 Too Many Requests

Bulk size would push you past your monthly quota and your plan does not allow overage.

{
 "detail": "Bulk of 50 would exceed your monthly quota (480/500 used)."
}

Plan caps reference

Planmax_bulk_items
Freen/a (endpoint disabled)
Starter50
Pro1 000
Agency5 000
Enterprise5 000

Hard limits

LimitValue
Max items per batch5 000 (or plan cap, whichever is lower)
Max ZIP size100 MB
Max single item pixels9 000 x 9 000
Max client timeout we suggest180 s for 5 000 items

Quota accounting

  • One quota credit per item.
  • Counter is bumped atomically once per batch (single UPDATE for the whole batch).
  • Cache hits still count.
  • Failed batches (422, 429) consume zero quota.

Examples

Python

import requests

items = [
{"data": f"https://yourbrand.com/c/{i:04d}", "size_inches": 3}
for i in range(50)
]

r = requests.post(
"https://api.qrstudio.agency/api/v1/generate/bulk/",
headers={
"X-Api-Key": os.environ["QRSTUDIO_API_KEY"],
"Content-Type": "application/json",
},
json={"items": items},
timeout=120,
)
if r.status_code == 422:
print("Errors:", r.json())
else:
r.raise_for_status()
open("batch.zip", "wb").write(r.content)

Node.js

import fs from "node:fs";

const items = Array.from({ length: 50 }, (_, i) => ({
data: `https://yourbrand.com/c/${String(i).padStart(4, "0")}`,
size_inches: 3,
}));

const r = await fetch("https://api.qrstudio.agency/api/v1/generate/bulk/", {
method: "POST",
headers: {
"X-Api-Key": process.env.QRSTUDIO_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ items }),
});
if (r.status === 422) {
console.error("Errors:", await r.json());
} else if (!r.ok) {
throw new Error(`HTTP ${r.status}`);
} else {
fs.writeFileSync("batch.zip", Buffer.from(await r.arrayBuffer()));
}

See also

On this page