Rendering, enrichment & batch jobs
api.zplcloud.com with an API key.
Authentication (X-API-Key header or Basic Auth), the error format, the X-ZplCloud-Request-Id header and the webhook callback are the same for every endpoint and are described on the API overview. Every endpoint on this page except the older ZPL render path accepts the optional query parameters webhookId=<id> and webhookResult=true|false; the event name is listed with each endpoint (payload and signature).
Design IDs
Design endpoints address a saved design as <name>.<id>: the design tag chosen when saving (lowercase letters, digits, _ and -) and the numeric design ID, separated by the last dot - for example shipping-label.42.
- Where to find it: after Save in the designer, the save dialog and the status bar show the full ID (shipping-label.42). The same ID works for ZPL, PDF, PNG and batch jobs.
- Scope: the design must belong to the company of the API key owner (keys without a company: the owner's e-mail). Name and number must both match - otherwise
404 Design "shipping-label" not found. - Format errors:
400 Invalid design ID - expected format "<name>.<id>" (e.g. my-label.42). - Records: a JSON array with one object per label; the keys are the binding names used in the design, e.g.
[{"sku":"A-1001","qty":"2"}].
Render a design as PDF
One PDF page per record at the label size and resolution of the design. Without a body (or with []) the design is rendered once with its own content. The quota is checked before rendering.
| Parameter | Type | Notes |
|---|---|---|
designId (path) | string | <name>.<id> |
| body | array | Optional. Records, max 2,000 per call and 5 MB. Larger runs: batch jobs. |
webhookId, webhookResult | query | Optional webhook callback, event api.design.pdf. |
| Status | Error |
|---|---|
| 400 | Invalid design ID, invalid JSON, PDF rendering failed: …, invalid webhookId. |
| 401 | Invalid or missing API key. |
| 404 | Design not found (or not in the key owner's scope). |
| 413 | Body over 5 MB, or more than 2,000 records. |
| 429 | Monthly label quota used up, see quota. |
Renders: one per page. Webhook api.design.pdf, summary { design, format: "pdf", pages, bytes, widthMm, heightMm, dpi }; with webhookResult=true the PDF comes along as base64.
Render a design as PNG
One label as PNG. The body is the same optional records array; record selects the record by zero-based index.
| Parameter | Type | Default | Notes |
|---|---|---|---|
designId (path) | string | - | <name>.<id> |
record (query) | int | 0 | Index into the records array. Out of range: 400 record must be between 0 and n. |
| body | array | - | Optional records, max 5 MB. |
webhookId, webhookResult | query | - | Optional, event api.design.png. |
Response: 200 image/png, file name <name>.<id>.png. Errors: 400, 401, 404, 413 and 429 as for PDF. Renders: 1. Webhook api.design.png, summary { design, format: "png", record, bytes, widthMm, heightMm, dpi }.
Render a design as ZPL
The existing ZPL path: same design ID and records array, response application/x-zpl; charset=utf-8 with one ^XA…^XZ block per record. Limits: 500 records, 1 MB body. Like PDF and PNG it is checked against the monthly label quota: one render per record (at least 1), 429 { error, hint, limit, used, requested } when the quota is used up. It also shows up in the API statistics and has no webhook callback. For PDF output, printing or more records, use the endpoints above (every plan) or a batch job (Pro plan).
Label enrichment
Adds your own text fields - SKU, bin location, quantity, PO, free text - to every label of ZPL that comes from somewhere else: Amazon, UPS, FedEx, DHL, USPS. The original commands stay byte-identical; for each label the overlay commands are inserted right before its closing ^XZ. The carrier barcode is never touched.
Feature switch
The endpoint exists only while label enrichment is enabled on zplCloud. Otherwise it answers 404 Label enrichment is not available.
Pro plan
Label enrichment calls require the Pro plan of the key owner. Other plans get 403 before the body is read - a requested webhook callback reports the failure. If the plan cannot be read, the answer is 503.
Request body
| Field | Type | Default | Notes |
|---|---|---|---|
zpl | string | required | Carrier or Amazon ZPL with one or more ^XA…^XZ labels, max 4,000,000 characters. |
dpi | int | 203 | Resolution of that ZPL: 203, 300 or 600. Converts millimetres to dots. |
fields | array | required | 1 to 50 overlay fields (table below). |
records | array | - | Objects of column → value, max 10,000. Values must be JSON strings. |
clearZone | bool | false | Paint a white box first, so overlays stay readable on printed areas. |
zoneX, zoneY, zoneW, zoneH | mm | 0 | Position and size of the white box; drawn only when zoneW and zoneH are above 0. |
| Field property | Type | Default | Notes |
|---|---|---|---|
x, y | mm | - | Top-left corner of the text block (^FO). |
w | mm | - | Width of the text block (^FB), at least one character height. |
h | mm | - | Height. Lines in the block = h ÷ font height, rounded down, at least 1. |
fontPt | number | 9 | Font size in points, scalable font ^A0; dots = fontPt × dpi / 72 (min. 6). |
align | string | L | L, C or R within w. |
column | string | - | Take the value from the label's record. Key match is case-insensitive, a leading $ is ignored; a missing key prints nothing. |
text | string | - | Static text, used when no column is set or the label has no record. |
label | string | - | Optional prefix, printed as label: value. |
Fields whose value ends up empty are skipped. Text is sent as UTF-8 (^CI28); ^ and ~ in values are hex-escaped with ^FH, so data cannot inject commands.
How records map to labels
- The ZPL is split into its
^XA…^XZblocks; record n goes to label n (both counted from 0, in order). - Exactly one record: it applies to every label (e.g. the same PO on all parcels of a shipment).
- No records: only
textfields are printed, on every label. - Fewer records than labels: the remaining labels get only their static
text. Extra records are ignored. - Anything between the blocks is kept as is. ZPL without a
^XA…^XZblock comes back unchanged withlabels: 0.
Response: JSON { "zpl": "…", "labels": 2 }; with ?format=zpl the raw ZPL as application/x-zpl; charset=utf-8, ready for the printer. Renders: one per label (at least 1), never blocked by the quota. Webhook api.enrich.completed, summary { labels, fields, records, dpi, zplBytes }.
| Status | Error |
|---|---|
| 400 | zpl is required …, fields is required …, Too many fields (max 50)., dpi must be 203, 300 or 600., format must be json (default) or zpl., invalid JSON (e.g. a number instead of a string in records). |
| 403 | Label enrichment calls require the Pro plan. - the key owner is not on the Pro plan (body with plan and hint). |
| 404 | Label enrichment is not enabled. |
| 413 | zpl over 4,000,000 characters, more than 10,000 records, or body over 24 MB. |
| 503 | The plan could not be checked (database) - retry later. |
Example: SKU and bin on a carrier label
A 4 × 6 inch carrier label (101.6 × 152.4 mm, 203 dpi) gets the SKU on the left and the bin location on the right, at the bottom on a white strip:
Each label gets ^FO40,1111^A0N,39,39^FB480,1,0,L,^CI28^FDSKU: A-1001^FS and the right-aligned bin field before its ^XZ.
Batch jobs
Renders a saved design with many records in one call: ZPL or PDF output, an optional pick list PDF and optional printing. Synchronous by default; "async": true answers immediately and calls the webhook when the job is done.
Pro plan
Batch jobs - including pick lists and printing - require the Pro plan of the key owner. Other plans get 403 before the body is read - a requested webhook callback reports the failure. If the plan cannot be read, the answer is 503. Job status, output and pick list (GET /v1/batch/jobs/…) are not plan-checked; jobs only exist if they were started on Pro. On other plans, render up to 500 records per call as ZPL or 2,000 as PDF.
| Field | Type | Default | Notes |
|---|---|---|---|
records | array | required | 1 to 10,000 objects, one per label (PDF output: max 5,000). Values may be strings, numbers or booleans. |
output | string | zpl | zpl, pdf or none. none needs a printer or a pickList. |
printer | string | - | Print target wl:{id}, Weblink serial, rp:{id} or vp:{id}, see printing. |
pickList | object | - | Pick list PDF from the same records, see pick list. |
async | bool | false | true: 202 with jobId, the job runs in the background. |
Body max 32 MB. Checks run in this order before anything is rendered: plan (Pro), body, records, output, design, quota (number of records), printer target - so a typo in the printer fails right away. Query parameters webhookId / webhookResult: event api.batch.completed.
Printing
| Target | Printer | Delivery |
|---|---|---|
wl:{id} or serial | Weblink cloud printer | Through the printer's Weblink connection. Must be visible to the key owner; a blocked printer answers 409. |
rp:{id} | Remote printer | Through the zplCloud CLI agent (zplcloud proxy) in your network; the agent must be online. |
vp:{id} | Virtual printer | Lands in the virtual printer's history; max 512 KB per block. Only when virtual printers are enabled. |
Targets come from GET /v1/printers (printers bound to the key) or GET /v1/company/printers (all printers of the company) - use the target field of an entry. Labels are sent in blocks of 100, one request per block. The first failed block stops printing: the job ends with print_failed, print.error holds the reason and sentChunks / sentLabels show how far it got. Output and pick list are delivered anyway; a print_failed job answers HTTP 200 and counts all records.
Target errors: 400 Invalid remote printer target - expected rp:{id}. (same for vp: and wl:), 400 Virtual printers are not available., 404 Printer not found. / Remote printer not found. / Virtual printer not found., 409 The printer is blocked.
Pick list
| Field | Type | Default | Notes |
|---|---|---|---|
title | string | design ID | Heading on page 1. |
subtitle | string | date, time, rows | Default: 2026-09-11 10:15 UTC · 250 Positionen. |
columns | string[] | all keys | Columns in this order; default all keys of the first record. Max 20. |
groupBy | string | - | Groups rows by this column (group row column: value), groups sorted. |
sortBy | string | - | Sorts rows (within groups) naturally: A-7 before A-10. |
checkbox | bool | true | Tick box at the start of each row. |
paper | string | A4 | A4 or Letter. |
Available only when pick lists are enabled (otherwise 400 Pick lists are not available.). A pick list holds at most 5,000 records; non-text values are printed as JSON text.
Synchronous response
jobIdequals theX-ZplCloud-Request-Idheader.statusissucceededorprint_failed.outputisnullfornone; PDF output comes asbase64.pickListandprintarenullwhen not requested.outputUrlisnullwhen the output is larger than 20 MB (it is then only in this response).- Rendering errors:
400 { "error": "Rendering failed: …", "jobId": "…", "requestId": "…" }.
Asynchronous jobs
- All checks (plan, records, design, quota, printer target) run before the 202, so those errors still come back directly.
- At most two batch jobs run at the same time per server; further jobs wait as
queued. - When the job is done the webhook is called with
api.batch.completed;requestIdis thejobId. WithwebhookResult=truetheresultholds the complete synchronous response as JSON (up to 10 MB). - The payload's
statusfollows the HTTP result: aprint_failedjob arrives as"status": "succeeded"withsummary.jobStatus: "print_failed"andsummary.printError. Checksummary.jobStatus. - Without
webhookId, poll thestatusUrl. Renders are counted when the job finishes, and only if it did not fail. - A server restart cancels running jobs:
failed,The job was cancelled (server restart).
| Status | Meaning |
|---|---|
queued | Accepted, waiting for a free slot. |
running | Rendering output and pick list, then printing. |
succeeded | Done; all blocks were accepted by the printer (if one was given). |
print_failed | Output and pick list are ready, but a print block failed (print.error). |
failed | Rendering failed or the job was cancelled (error). |
| Limit | Value |
|---|---|
| Records per job | 10,000 (PDF output 5,000, pick list 5,000) |
| Request body | 32 MB |
| Print block | 100 labels |
| Parallel jobs | 2 per server, others wait |
| Stored output / retention | 20 MB / 1 hour |
End to end: async job printing to rp:12
1. Find the target:
2. Start the job with webhook 12 (GET /v1/webhooks lists the IDs; the key owner needs the Pro plan):
3. The call returns 202 with the jobId right away (see above). 4. When the last block has reached the printer, your webhook URL receives a POST with the headers X-ZplCloud-Event: api.batch.completed, X-ZplCloud-Delivery, X-ZplCloud-Timestamp and X-ZplCloud-Signature:
5. Fetch the pick list within the hour: GET /v1/batch/jobs/3f0c9a7e-…/picklist.
Job status and downloads
| Route | Response | Webhook event |
|---|---|---|
| GET /v1/batch/jobs/{jobId} | Status JSON | api.batch.status |
| GET /v1/batch/jobs/{jobId}/output | ZPL (application/x-zpl) or PDF, up to 20 MB | api.batch.output |
| GET /v1/batch/jobs/{jobId}/picklist | Pick list PDF | api.batch.picklist |
- Jobs are kept in memory for 1 hour (
expiresUtc) on the server that accepted them. After that, or on another server instance:404 Batch job not found. Jobs are kept for 1 hour on the server that accepted them. - Only the same company (keys without a company: the same e-mail) can see a job.
/outputanswers 404 while the job is running, foroutput: noneand for output over 20 MB;/picklistanswers 404 while running or withoutpickList.- Status and downloads count no renders and are not plan-checked.
Quota and render counting
| Endpoint | Checked (429) | Renders counted |
|---|---|---|
| POST /v1/zpl/render/design/{designId} | yes | one per record (at least 1) |
| POST /v1/pdf/render/design/{designId} | yes | one per page (records, at least 1) |
| POST /v1/png/render/design/{designId} | yes | 1 |
| POST /v1/batch/design/{designId} | yes, number of records | one per record, also for print_failed |
| POST /v1/enrich/zpl | no, never blocked | one per label (at least 1) |
| GET /v1/batch/jobs/… | no | 0 |
Batch jobs and label enrichment also require the Pro plan (403, see above). Calls that fail (HTTP 400 and above) count nothing. When the monthly label quota is used up, checked endpoints answer before any work is done:
Plan limits, top-ups, buffered counting and GET /v1/quota: API overview → quota. The ZPL tools API is never blocked either.