Batch generating QR codes at scale with the Qrop API
By Qrop Team ·
Calling /api/v1/generate once for every code in a print run works, right up until the print run has a few hundred items in it. At that point the bottleneck isn't Qrop, it's the overhead of a few hundred separate HTTP round trips for work that's fundamentally the same shape every time. That's what /api/v1/generate/batch is for: up to 25 QR codes back from a single request, with one reconciled credit total instead of 25 separate ones to track.
This guide is a mechanics walkthrough, not a plans overview: exactly what a batch request looks like, what's shared across every item in it versus what has to be set per item, how the credit accounting works, and how to actually scale beyond one batch's 25-item ceiling once your volume calls for it. If you haven't decided yet whether your workflow even needs the API rather than the dashboard, the practical uses guide covers that fork first.
Everything here matches the contract documented in the API spec. Treat this as the walkthrough version of that reference, with the reasoning behind each limit included.
When batch beats one call per code
Batch is the right tool exactly when you already know every destination up front: a print run of product tags, a stack of event badges, a catalogue import. If destinations arrive one at a time as real events happen, a checkout, a ticket sale, a signup, batch doesn't fit that shape at all; call /api/v1/generate once per event as it happens instead. Batching things that aren't actually batched in reality just adds a queue you have to manage yourself.
The saving isn't rendering speed, a batch of 25 takes roughly 25 times as long to rasterize as one code, since each item still has to go through the same server-side rendering step. The saving is in request overhead and bookkeeping: one HTTP round trip, one credit total, one success/failure count, instead of 25 of each.
The request shape: shared settings, per-item data
A batch request has two tiers. The items array holds up to 25 entries, and each entry needs only data and type, the two fields that actually differ between a batch of product tags pointing at 25 different URLs. Everything else, format, resolution, and an optional logo, is set once at the top level and applies to the whole batch.
That split isn't a limitation so much as a match for the real use case: a print run of 25 tags wants identical branding, identical size, identical output format, with only the destination changing per item. If any of those shared settings actually need to vary within one run, that's a sign you need two separate batch calls, not one call with per-item overrides that don't exist.
- Per item: data (the content to encode) and type (url, text, vcard, wifi, email, sms, phone, or geo).
- Shared across the batch: format (png, svg, or pdf), resolution, and an optional logoUrl or logoDataUrl.
- Not available in batch at all: per-item foreground/background/finder colours, module shape, or error correction override, those are single-call (/api/v1/generate) features.
Reading the response and reconciling credits
The response comes back as a results array, one entry per input item in the same order, each carrying its own id, base64 image, and a success flag, alongside a single creditsConsumed total and a failedCount for the whole batch. Reconciling a 25-item print run against your billing is a one-line check against creditsConsumed rather than summing 25 individual charges.
Batch doesn't process partially on a credit shortfall: if your account can't cover the full batch, the entire request is rejected with a 402 before anything is generated, so you never end up in a state where 18 of 25 items succeeded and you have to work out which 7 to retry. failedCount in a successful response instead reflects per-item validation issues, like an item whose data fails the contrast or format checks, not credit exhaustion.
The one-logo-per-batch rule
A batch call takes a single logoUrl or logoDataUrl, stamped identically onto every item in that call. There's no field for assigning a different logo to individual items within the same batch, the shared-settings tier described above applies to logos too.
In practice this rarely matters: a print run almost always wants one consistent brand mark across every code. When it doesn't, when part of a run genuinely needs a second logo or no logo at all, split that into a second batch call rather than looking for a per-item override that doesn't exist. Logos are also batch-incompatible with SVG output specifically: requesting format: "svg" with a logo set returns a 422 with no credit consumed, since the SVG renderer doesn't composite raster logo assets the way the PNG/PDF path does.
Why a bigger batch size wouldn't help
It's tempting to assume a larger per-request cap, say 100 items instead of 25, would mean faster throughput. It wouldn't. Items inside one batch call are rasterized sequentially on the server, resvg's PNG/PDF rendering is synchronous, so a 100-item batch would simply take four times as long to return and produce a much larger response payload, with no code generated any faster than in a 25-item call.
The actual lever for scaling past one batch isn't a bigger batch, it's more batches running in parallel from your own client. Each batch call is independent and can land on a different server instance, so issuing several 25-item batch calls concurrently gets you real parallel throughput in a way that one giant batch never would.
Scaling past 25: parallelizing batch calls
For volumes measured in the hundreds or thousands, split the work into 25-item chunks and fire multiple batch calls concurrently, staying inside your plan's rate limit as you do. GET /api/v1/usage returns your current rateLimit (requests per second and per minute), and that's the number to design your concurrency around, not a guess.
The plans scale meaningfully here: Starter allows 5 requests/second, Pro 15, Growth 25, and Enterprise 100. A Growth-plan integration parallelizing 25-item batches at its 25 req/sec ceiling can generate roughly 625 codes/sec in aggregate, well beyond what a naive loop of single-item calls at the same rate limit would achieve, since each of those 25 requests per second is doing 25x the work of a single-item call.
- Chunk your destination list into groups of 25 (or fewer for the last, partial group).
- Fire chunks concurrently up to your plan's requests-per-second ceiling, then wait for the next second's window rather than bursting past it.
- Watch for 429 responses with a Retry-After header if you do exceed the limit, and back off for that duration rather than retrying immediately.
- Sum each batch's creditsConsumed as you go if you need a running total rather than waiting for the whole job to finish before checking /api/v1/usage.
Worked example: a 500-item product catalogue import
Say a marketplace is importing 500 new product listings and needs a QR code linking to each one, same logo, same 1024px PNG output, across the board. That's 20 batch calls of 25 items each, not 500 single calls.
On a Pro plan (15 requests/second), issuing all 20 batch calls at once would exceed the rate limit, so the practical approach is 15 concurrent calls in the first second and the remaining 5 in the next, or simply queueing all 20 with a concurrency cap of 15 and letting the queue drain naturally. Either way, the whole import finishes in roughly 2 seconds of request time plus rasterization, not the tens of seconds 500 sequential single-item calls would take.
Once it's done, creditsConsumed across the 20 responses sums to 500, one credit per code, identical to what 500 single calls would have cost. Batch doesn't change the price, it changes how many round trips and how much bookkeeping it takes to get there.
Frequently asked questions
How many QR codes can one batch request generate?
Up to 25 items per call. Requesting more than 25 returns a 400 before anything is generated. For higher volumes, split the work into multiple 25-item batch calls and issue them concurrently within your plan's rate limit rather than looking for a larger single-call limit.
Can each item in a batch have its own logo or colours?
No. Format, resolution, and logo are set once for the whole batch and apply to every item in it. Per-item colours, module shape, and error-correction overrides aren't available in batch at all, those are single-call (/api/v1/generate) features. If different items genuinely need different branding, split them into separate batch calls.
Does a bigger batch process faster than several smaller ones?
No, the opposite in terms of latency per call. Items within one batch are rasterized sequentially server-side, so a larger batch simply takes proportionally longer to return. Real throughput comes from running multiple batch calls in parallel from your own client, not from requesting a bigger batch size.
What happens if I don't have enough credits to cover a full batch?
The entire request is rejected with a 402 before any item is generated, there's no partial processing. This means a batch response's failedCount only ever reflects per-item validation failures (like an invalid data type or a contrast rejection), never a credit shortfall partway through the batch.
Are tracked (dynamic) QR codes supported in batch requests?
No. Tracked codes each need their own permanent redirect record, which is a different shape of resource than a one-off image and doesn't fit the batch model's scale assumptions. Create tracked codes individually via /api/v1/tracked, in a loop on your end if you need several, rather than through /generate/batch.
Related guides
Ready to create your QR code?
Generate a free QR code in seconds: custom colours, logos, and PNG, SVG or PDF output.
Open the generator