zpl.tools
Guides

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.png

An 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