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
| Plan | max_bulk_items |
|---|---|
| Free | n/a (endpoint disabled) |
| Starter | 50 |
| Pro | 1 000 |
| Agency | 5 000 |
| Enterprise | 5 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, notlogo_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:
| File | Content |
|---|---|
qr-0001.png (or .svg) | First item's render |
qr-0002.png | Second item's render |
| ... | ... |
qr-NNNN.png | Nth item's render |
manifest.json | Mapping 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
| Limit | Value | Why |
|---|---|---|
| Max items per batch | 5 000 | Plan-capped per tier |
| Max ZIP size | 100 MB | Refuses runaway high-DPI batches |
| Max single item size | 9 000 x 9 000 px | Same 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
- API reference: bulk endpoint
- Guide: bulk export to ZIP
- Caching: why duplicate items in a batch are essentially free