MuleSoft integration
There is no LabelZoom connector on Anypoint Exchange, and none is needed: the API is a single POST with the document as the request body, which the stock HTTP Request connector handles directly. This guide covers the conversion API and the Cloud Print API from a Mule 4 flow.
Prerequisites
Section titled “Prerequisites”- Mule runtime 4.4 or later, Anypoint Studio 7.x
- HTTP Connector 1.7+ (bundled with Studio)
- Optionally, a LabelZoom API key — see Authentication
An API key is optional: without one, every request runs on the free tier and the output is watermarked, so every snippet below works as-is. Add the key when you’re ready to ship.
Configure the connection
Section titled “Configure the connection”One request config, reused by every flow:
<http:request-config name="LabelZoom_Config" responseTimeout="60000"> <http:request-connection protocol="HTTPS" host="api.labelzoom.com" port="443" maxConnections="8"/></http:request-config>Size maxConnections against your plan’s throughput cap — see Rate limits.
Keep the key out of the flow XML. Put it in a secure properties file and read it with p():
<secure-properties:config name="Secure_Props" file="config-${mule.env}.yaml" key="${encryption.key}"/>labelzoom: apiKey: "![encrypted...]"Convert ZPL to PDF
Section titled “Convert ZPL to PDF”<flow name="zpl-to-pdf"> <http:listener config-ref="HTTP_Listener_Config" path="/label/pdf"/>
<http:request method="POST" path="/api/v2/convert/zpl/to/pdf" config-ref="LabelZoom_Config" requestStreamingMode="NEVER" outputMimeType="application/octet-stream"> <http:body><![CDATA[#[payload]]]></http:body> <http:headers><![CDATA[#[output application/javavar apiKey = p('secure::labelzoom.apiKey') default ''---{ "Content-Type": "text/plain", "Accept": "*/*", "User-Agent": "labelzoom-mulesoft/1.0", ("Authorization": "Bearer " ++ apiKey) if (apiKey != '')}]]]></http:headers> </http:request>
<file:write path="label.pdf"/></flow>POST ZPL to http://localhost:8081/label/pdf and label.pdf lands next to your app. The
response body is the converted file itself — there is no JSON envelope to unwrap.
Request headers
Section titled “Request headers”| Header | Rule |
|---|---|
Accept |
Accept: */* is the one value that needs no per-target logic. The target’s exact media type and a wildcard subtype (image/*) also work; charset and q parameters are ignored. |
Content-Type |
Must be the source format’s exact media type — text/plain for the printer languages, application/pdf for PDF, image/png for PNG, and so on. Anything else returns 400. See the table below. |
User-Agent |
Must not begin with LabelZoomStudio/. The server reads that prefix as a signal that the caller is a field-deployed LabelZoom Studio client and adjusts conversion defaults accordingly. |
Two connector settings matter:
requestStreamingMode="NEVER"makes Mule send a realContent-Lengthinstead of chunking. The API rejects a body-less request outright, and chunked transfer from a lazily-evaluated payload is the usual way to send one by accident.outputMimeType="application/octet-stream"stops Mule from coercing the response toward a String based on the response content type — several targets do not survive that. See Response content types.
Conversion parameters
Section titled “Conversion parameters”Every option — DPI, rotation, label size, variable data — travels in one params query
parameter holding URL-encoded JSON. Build it in DataWeave and let the connector do the encoding:
<http:query-params><![CDATA[#[output application/java---{ "params": write({ dpi: 300, rotation: 90 }, "application/json")}]]]></http:query-params>The server also accepts flat dot-notation parameters (?dpi=300&label.width=4), and JSON
values such as ?data=[{},{}] carry that way too. Where the two spellings overlap, the
dot-notation value wins. Either is fine; one serialization path is simply easier to maintain.
Two rules for building the object:
- Send only what you actually set. Don’t hardcode
dpi: 203into a shared sub-flow — it’s already the server default, and pinning it means you won’t inherit a future change. - Omitting
labeltriggers server-side size detection from the source document — there is no client-side4 × 6default. Setlabel.width/label.height(in inches) only when you need to override the detected size.
The full parameter list — scaling, colorMode, darkness, position, pdf.*, zpl.* — is
in Conversion parameters.
Fill variable data from a record set
Section titled “Fill variable data from a record set”One call turns a collection of records into a multi-page PDF: each entry in the data array
produces one label.
<flow name="picklist-to-labels"> <db:select config-ref="ERP_DB"> <db:sql>SELECT item_code, qty, location FROM pick_lines WHERE wave_id = :wave</db:sql> </db:select>
<set-variable variableName="labelData" value="#[payload]"/> <set-payload value="#[vars.zplTemplate]"/>
<http:request method="POST" path="/api/v2/convert/zpl/to/pdf" config-ref="LabelZoom_Config" requestStreamingMode="NEVER" outputMimeType="application/octet-stream"> <http:body><![CDATA[#[payload]]]></http:body> <http:query-params><![CDATA[#[output application/java---{ "params": write({ data: vars.labelData map { sku: $.item_code, qty: $.qty as String, loc: $.location } }, "application/json")}]]]></http:query-params> <http:headers><![CDATA[#[output application/javavar apiKey = p('secure::labelzoom.apiKey') default ''---{ "Content-Type": "text/plain", "Accept": "*/*", "User-Agent": "labelzoom-mulesoft/1.0", ("Authorization": "Bearer " ++ apiKey) if (apiKey != '')}]]]></http:headers> </http:request></flow>data is always an array of objects, even for a single label.
Convert a PDF to ZPL
Section titled “Convert a PDF to ZPL”Same request shape, different path and content type:
<file:read path="shipping-label.pdf" outputMimeType="application/pdf"/>
<http:request method="POST" path="/api/v2/convert/pdf/to/zpl" config-ref="LabelZoom_Config" requestStreamingMode="NEVER" outputMimeType="application/octet-stream"> <http:body><![CDATA[#[payload]]]></http:body> <http:query-params><![CDATA[#[output application/java---{ "params": write({ pdf: { pageNumber: 0 } }, "application/json") }]]]></http:query-params> <http:headers><![CDATA[#[output application/java---{ "Content-Type": "application/pdf", "Accept": "*/*", "User-Agent": "labelzoom-mulesoft/1.0" }]]]></http:headers></http:request>pdf.pageNumberis 0-based; omit it to convert every page.pdf.conversionModedefaults toIMAGE, which rasterizes each page and preserves appearance exactly.NATIVEextracts the underlying vector and text content instead.
If your payload is already base64 — a common shape when the PDF arrived as a field in an
upstream JSON or SOAP message — you don’t have to decode it. Every image and PDF source accepts
a base64 body sent as Content-Type: text/plain. Send the string as-is and change the one
header.
Response content types
Section titled “Response content types”The response body is the converted file. Keep it as bytes:
| Source format | Request Content-Type |
Target format | Response type |
|---|---|---|---|
zpl epl ipl tspl dpl sbpl |
text/plain |
zpl epl ipl tspl dpl sbpl |
text/plain |
xml |
application/xml |
xml |
application/xml |
json |
application/json |
json |
application/json |
pdf |
application/pdf |
pdf |
application/pdf |
png |
image/png |
png |
image/png |
bmp |
image/bmp |
bmp |
image/bmp |
gif |
image/gif |
gif |
image/gif |
jpeg / jpg |
image/jpeg |
jpeg |
image/jpeg |
url |
text/plain (body is the URL) |
— | — |
jpg and url are source-only; there is no url target. Image and PDF sources also accept a
base64 body as text/plain.
Handle errors and retries
Section titled “Handle errors and retries”By default the HTTP Request connector throws on any non-2xx status, and Mule’s error-type mapping doesn’t cover every status the API returns. Widening the success validator and branching explicitly gives you the full picture:
<http:request method="POST" path="/api/v2/convert/zpl/to/pdf" config-ref="LabelZoom_Config"> <!-- ... body, headers, query-params ... --> <http:response-validator> <http:success-status-code-validator values="200..299,400..599"/> </http:response-validator></http:request>
<choice> <when expression="#[attributes.statusCode == 200]"> <file:write path="label.pdf"/> </when> <otherwise> <logger level="ERROR" message="#[ 'LabelZoom ' ++ (attributes.statusCode as String) ++ ' reqId=' ++ (attributes.headers['x-lz-request-id'] default 'none') ++ ' body=' ++ (payload as String)]"/> <raise-error type="APP:LABEL_CONVERSION_FAILED"/> </otherwise></choice>| Status | Cause | Retry? |
|---|---|---|
400 |
The label or file didn’t parse, Content-Type doesn’t match the source, or Accept names a type this target can’t produce |
No |
401 |
Malformed Authorization header |
No |
403 |
Valid key, but the plan doesn’t allow this path — the message matches paid feature |
No |
406 |
Accept names a media type the endpoint never produces (e.g. application/zip) |
No |
413 |
Body over 1 MB on the free tier | No |
429 |
Rate limited; honor Retry-After |
Yes |
5xx |
Transient | Yes |
If you prefer the default throwing behavior, the reliably-mapped error types are
HTTP:UNAUTHORIZED, HTTP:FORBIDDEN, HTTP:TOO_MANY_REQUESTS, HTTP:INTERNAL_SERVER_ERROR,
HTTP:CONNECTIVITY and HTTP:TIMEOUT.
Retry only 429, 5xx, and connectivity failures — a malformed label fails the same way on
every retry:
<until-successful maxRetries="3" millisBetweenRetries="1000"> <try> <http:request .../> <error-handler> <on-error-continue type="HTTP:BAD_REQUEST, HTTP:FORBIDDEN, HTTP:UNAUTHORIZED"/> </error-handler> </try></until-successful>Log x-lz-request-id on every path, success and failure — it’s the handle support asks for
(it looks like 2026/08/15/120000--3f2b8c1e-…). Read it lowercase from attributes.headers:
Mule normalizes response header names, and the header is spelled inconsistently across the
API’s own surfaces, so don’t match on case. See Errors.
Print to a cloud printer
Section titled “Print to a cloud printer”The Cloud Print API pushes a label to a printer connected through the LabelZoom Print Agent — no VPN, and no port 9100 reachability from CloudHub:
<http:request method="POST" path="/api/v3/printers/{printerId}/print" config-ref="LabelZoom_Config" requestStreamingMode="NEVER"> <http:body><![CDATA[#[payload]]]></http:body> <http:uri-params><![CDATA[#[{ printerId: p('labelzoom.printerId') }]]]></http:uri-params> <http:query-params><![CDATA[#[{ sourceFormat: 'zpl' }]]]></http:query-params> <http:headers><![CDATA[#[output application/java---{ "Content-Type": "text/plain", "Accept": "*/*", "Authorization": "Bearer " ++ p('secure::labelzoom.apiKey'), "Idempotency-Key": correlationId}]]]></http:headers></http:request>Three things this flow relies on:
- An
Idempotency-Keyheader. Without one, wrapping the request in<until-successful>means a timeout on a print that actually succeeded reprints the label. Repeating a key with the same request returns the original job; reusing one for a different request (another document, printer, orcopies) is refused with a422— see Idempotency.correlationIdis a natural source;uuid()works too. sourceFormatfor the printer languages. ZPL, EPL, IPL, TSPL, DPL and SBPL all arrive astext/plain, so the API can’t tell them apart from the content type alone. PDF and images are detected fromContent-Type(or sniffed from magic bytes), sosourceFormatis optional there.- Format conversion happens in transit. A PDF sent to a printer configured for ZPL is
converted on the way. Any conversion parameter also works
as a query parameter on this endpoint —
rotation=90,dpi=300.
The response is JSON:
{ "jobId": "9f2c…", "status": "dispatched" }dispatched means the job reached an online agent. queued means every agent bound to that
printer is offline — the job is held and drains when one reconnects, so treat it as accepted,
not failed. Poll GET /api/v3/jobs/{jobId} for progress: queued, dispatched, printing,
completed, failed. Path-specific errors: 403 the printer isn’t yours, 404 no such
printer, 502 the document couldn’t be converted to the printer’s native format.
Production notes
Section titled “Production notes”Validate the key by converting, not by status code
Section titled “Validate the key by converting, not by status code”The conversion endpoint treats an unresolvable credential as anonymous rather than returning
401, so a typo’d key returns a perfectly healthy 200 carrying a watermarked free-tier label.
During setup, convert one label and look at it. See
A bad API key does not fail.
Keys created from the dashboard are always test keys (lz_test_) — watermarked, never
billed. Production keys (lz_live_) are provisioned by sales/support on Pro and above. An app
that “works fine but watermarks in prod” is nearly always this. See
Test keys and live keys.
Rate limits
Section titled “Rate limits”The throughput cap is shared by everything using the same API key, so horizontal scaling does
not buy throughput: every worker in a CloudHub cluster sharing one key draws on the same budget.
Size maxConnections and the concurrency of any <parallel-foreach> or scatter-gather with
that in mind, and handle 429 with backoff rather than designing around never seeing one.
Monthly volume limits are per plan — see Rate limits and plans.
Secrets
Section titled “Secrets”Keep the key in a secure properties file or Anypoint Secrets Manager. It’s a bearer credential: anyone holding it can spend your quota.
See also
Section titled “See also”- Supported formats — the full source/target matrix, content types, and URL input
- Conversion parameters — every option that goes in
params - Errors — status codes and retry guidance
- Cloud Print API — the full printer, job, and agent endpoint reference
- Authentication — keys and bearer tokens
- Java Quickstart — Mule runs on the JVM, so the Java SDK is available from a custom component if you’d rather not hand-build requests
- Swagger UI — the interactive reference