Handling multi-page retailer packets
Render a whole retailer label packet into one multi-page PDF in a single request.
A retailer packet is one document that holds many labels: a carton label, a packing slip, a GS1 pallet label, and often more. The API has no page-count limit and no request-body-size limit, so one request renders a whole packet.
1. Build the packet
A ZPL document is a sequence of labels, each between ^XA and ^XZ:
^XA^FO50,50^A0N,40,40^FDCarton 1 of 3^FS^FO50,120^BY3^BCN,100,Y,N,N^FD00123456789012345675^FS^XZ
^XA^FO50,50^A0N,40,40^FDCarton 2 of 3^FS^FO50,120^BY3^BCN,100,Y,N,N^FD00123456789012345682^FS^XZ
^XA^FO50,50^A0N,40,40^FDCarton 3 of 3^FS^FO50,120^BY3^BCN,100,Y,N,N^FD00123456789012345699^FS^XZ
Each ^XA…^XZ block becomes one page in the output PDF, in order.
2. Render the packet to one PDF
Call the render endpoint without a label index and request
application/pdf.
cURL
curl -X POST "https://api.zpl.tools/compatibility/labelary/v1/printers/8dpmm/labels/4x6" \
-H "X-API-Key: your_api_key_here" \
-H "Accept: application/pdf" \
--data-binary "@packet.zpl" \
-o packet.pdf--data-binary "@packet.zpl" sends the file from disk unchanged. --data
strips the line breaks.
Python
import requests
api_key = "your_api_key_here"
with open("packet.zpl", "rb") as f:
zpl = f.read()
response = requests.post(
"https://api.zpl.tools/compatibility/labelary/v1/printers/8dpmm/labels/4x6",
headers={
"X-API-Key": api_key,
"Accept": "application/pdf",
},
data=zpl,
)
response.raise_for_status()
# The server reports how many labels it rendered.
print("labels in packet:", response.headers.get("X-Total-Count"))
with open("packet.pdf", "wb") as f:
f.write(response.content)Node
import { readFile, writeFile } from 'node:fs/promises';
const apiKey = 'your_api_key_here';
const zpl = await readFile('packet.zpl');
const response = await fetch('https://api.zpl.tools/compatibility/labelary/v1/printers/8dpmm/labels/4x6', {
method: 'POST',
headers: {
'X-API-Key': apiKey,
Accept: 'application/pdf',
},
body: zpl,
});
if (!response.ok) throw new Error(`Render failed: ${response.status}`);
console.log('labels in packet:', response.headers.get('x-total-count'));
const pdf = Buffer.from(await response.arrayBuffer());
await writeFile('packet.pdf', pdf);3. Confirm the page count
Every successful render returns an X-Total-Count header with the number of
labels in the document. Check it before you print or archive:
expected = 3 # cartons you built the packet for
got = int(response.headers["X-Total-Count"])
if got != expected:
raise RuntimeError(f"packet mismatch: expected {expected} labels, got {got}")4. Reprint one label from the packet
Request a single label by its zero-based index. PNG requires an index. PDF works with or without one.
# Second label (index 1) as a PNG
curl -X POST "https://api.zpl.tools/compatibility/labelary/v1/printers/8dpmm/labels/4x6/1" \
-H "X-API-Key: your_api_key_here" \
--data-binary "@packet.zpl" \
-o carton-2.pngAn index past the end of the document returns a 404 with a message like
ERROR: Requested 4th label but ZPL only generated 3 labels. The
X-Total-Count header still gives the number of labels.
Mixed label sizes
The 4x6 in the path applies to the whole request, but a packet can mix a 4×6
carton label with an 8.5×11 packing slip. Render each size group in its own
request and merge the PDFs on the client, or standardize the ZPL to one media
size. A per-label size override is planned. Tell us at
zpl.tools/contact if this blocks you.
Next steps
- API Reference — Rendering — full parameter and error detail.
- Quickstart — the single-label basics.
- ZPL command reference — build the label content itself.