QRQR Code Agency
Concepts

Bulk generation

Render up to 5000 QRs in one HTTP call and get a ZIP archive plus a manifest describing every item.

Bulk generation

POST /api/v1/generate/bulk/ renders up to plan.max_bulk_items QRs in a single call and returns a ZIP archive plus a manifest.json. It is the right tool when you have a large list of distinct QRs (campaign batches, per-customer codes, sticker production) and you do not want to pay N HTTP round-trips.

Why a dedicated endpoint

The single-render endpoint is fine for any one QR. But:

  • 5 000 individual POSTs hit our rate limiter and your network stack.
  • Quota accounting becomes complicated when one of the calls fails mid-batch.
  • ZIP packaging client-side requires every caller to reimplement the same ZIP writer.

The bulk endpoint solves all three: one POST, atomic quota accounting, ZIP packaged on our side.

Plan caps

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

Calling with more items than your cap returns 400 with {"items": "Plan '<plan>' caps bulk at <N> items per request, got <M>."}.

All-or-nothing batches

If any item fails validation (bad color, oversize, payload missing for vcard, etc.), the whole batch is rejected with 422 Unprocessable Entity and a per-index error map:

{
 "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 tells you how many items had already rendered when the first failure hit. We never emit a partial ZIP. The all-or-nothing rule keeps your accounting predictable: you paid N quota credits, you got N QRs.

Quota and overage

Bulk renders count one quota credit per item. A bulk of 50 spends 50 credits. The counter is bumped atomically once per batch (not 50 small UPDATEs).

If the batch would push you past your monthly cap and your plan does not allow overage (Free, Enterprise), the entire batch is refused with 429 Too Many Requests:

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

If your plan allows overage (Starter, Pro, Agency), the batch is accepted and the overage counter is incremented for the surplus.

What goes in items[]

Each item is a full GenerateRequest (same shape as POST /generate/):

{
 "items": [
 { "data": "https://example.com/1", "size_inches": 3 },
 { "data": "https://example.com/2", "size_inches": 3, "color": "#6c48e8" },
 {
 "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
 }
 ]
}

You can mix data types, sizes, formats, and styles freely. Each item is rendered independently and goes through the same render cache as the single endpoint, so duplicate items in a batch render exactly once.

info: Logos in bulk Bulk only accepts logo_url, not logo_file. Multipart uploads with N files are awkward to validate and not how packaging pipelines feed us payloads. Host the logo on a CDN and reference it via URL.

What you get back

A ZIP archive (Content-Type: application/zip) containing:

FileContent
qr-0001.png (or .svg)First item's render
qr-0002.pngSecond item's render
......
qr-NNNN.pngNth item's render
manifest.jsonMapping of index to filename plus per-item metadata

manifest.json is stored uncompressed inside the ZIP so a zero-dep ZIP reader can parse it without an inflate dependency:

{
 "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
 }
 ]
}

For dynamic items, the manifest entry adds a dynamic block:

{
 "index": 3,
 "filename": "qr-0004.png",
 "data_type": "dynamic",
 "dynamic": {
 "short_id": "aBc12dEf",
 "public_url": "https://q.qrstudio.agency/q/aBc12dEf/"
 }
}

Persist short_id for each item if you plan to PATCH destinations later or pull analytics.

Hard ceilings

LimitValueWhy
Max items per batch5 000Plan-capped per tier
Max ZIP size100 MBRefuses runaway high-DPI batches
Max single item size9 000 x 9 000 pxSame as /generate/

If your batch exceeds 100 MB of output, the API returns 400 with the explicit overflow error and no quota is consumed. Reduce size_inches or dpi, or split into smaller batches.

Performance

  • Items are rendered sequentially. Cache hits are essentially free.
  • A 50-item batch of similar QRs typically returns in 2-4 seconds.
  • A 5 000-item batch with 600 DPI and custom logos can take 90-180 seconds. Bump your client timeout accordingly.

Code examples

See Bulk export to ZIP for a full walk-through and the API reference for parameter details.

What is next

On this page