MuleSoft integration
MuleSoft sits in exactly the systems that produce and consume barcode labels: a WMS that emits ZPL, an ERP that emits a PDF, a TMS that needs one turned into the other. There is no LabelZoom connector on Anypoint Exchange — you don’t need one. 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 you get the free tier, watermarked, so every snippet below runs 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>maxConnections matters more than usual here; 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 PDF — there is no JSON envelope to unwrap.
The headers that matter
Section titled “The headers that matter”Accept: */* is the safe default. Naming the target’s exact media type works, but */*
is the one value valid for all eleven targets against every deployed server version — older
servers reject an exact image/gif, image/bmp or image/jpeg with a 406 from content
negotiation, before the handler ever runs, and Studio will cheerfully set one for you if you
let it infer the type.
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. See the
table below. Anything else is a 400.
Set a deliberate User-Agent. It must not begin with LabelZoomStudio/ — the server reads
that prefix as a backwards-compatibility signal for field-deployed LabelZoom Studio clients and
adjusts conversion defaults accordingly. Your integration is not Studio; don’t claim to be.
requestStreamingMode="NEVER" makes Mule send a real Content-Length instead of chunking. The
gateway rejects a body-less request outright, and chunked transfer from a lazily-evaluated
payload is the usual way to end up sending one by accident.
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>Prefer a single params object over dot notation. The server also accepts
?dpi=300&label.width=4, and it is tempting in Mule because it maps cleanly onto a
query-params map. It works — JSON values such as ?data=[{},{}] included — but one
serialization path with no per-field exceptions is the version that keeps working as you add
options, and it behaves the same against every deployed server version.
Two rules that change how you build that 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
labelis a feature, not an oversight. There is no client-side4 × 6default. Leavinglabelout is what triggers server-side size detection from the source document; supplying it defeats that. Setlabel.width/label.height(in inches) only when you genuinely need to override the detected size.
The full parameter list — scaling, colorMode, darkness, position, pdf.*, zpl.* — is
in Conversion parameters.
Filling variable data from a record set
Section titled “Filling variable data from a record set”This is where a Mule integration earns its keep: 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>Twelve pick lines in, a twelve-page PDF out, in one request. data is always an array of
objects even for a single label.
Converting a PDF to ZPL
Section titled “Converting a PDF to ZPL”Same 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.pageNumber is 0-based; omit it to convert every page. pdf.conversionMode is IMAGE
by default, which rasterizes each page and preserves appearance exactly; NATIVE extracts 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.
Keeping the payload binary
Section titled “Keeping the payload binary”Set outputMimeType="application/octet-stream" on the request, as every snippet above does.
Mule will otherwise coerce the response toward a String based on the response content type, and
several targets do not survive that.
| Source format | Request Content-Type |
Target format | Response type |
|---|---|---|---|
zpl epl tspl dpl |
text/plain |
zpl epl tspl dpl |
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) |
— | — |
Image and PDF sources also accept a base64 body as text/plain. jpg and url are
source-only; there is no url target.
One more surprise worth designing around: not every target returns every label. PDF and the
printer languages include all of them (as pages, or concatenated). png, bmp, gif, jpeg
and xml return only the first. On the free tier, only the first label renders normally
regardless of target — the rest are replaced with a watermark label.
Errors, retries, and request IDs
Section titled “Errors, retries, and request IDs”By default the HTTP Request connector throws on any non-2xx status, and Mule’s error-type mapping doesn’t cover every status LabelZoom 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 | What it means here | Retry? |
|---|---|---|
400 |
The label or file didn’t parse, or Content-Type doesn’t match the source |
No |
401 |
Malformed Authorization header |
No |
403 |
Valid key, but the plan doesn’t allow this path — the message matches paid feature |
No |
406 |
You sent an exact Accept. Send */* |
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. Retrying a malformed label fails
forever, three times as slowly:
<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 support handle — the
format looks like 2026/08/15/120000--3f2b8c1e-…. Read it lowercase from attributes.headers;
Mule normalizes response header names, but the header is spelled inconsistently across the
gateway’s own surfaces, so don’t match on case.
Printing from a Mule flow
Section titled “Printing from a Mule flow”The Cloud Print API pushes a label to a printer connected through the LabelZoom Print Agent — no VPN, 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>The Idempotency-Key header is not optional in a Mule app. Wrap that request in
<until-successful> without one and a timeout on a print that actually succeeded reprints the
label. Repeating a key returns the original job instead. correlationId is a natural source;
uuid() works too.
sourceFormat is required for the printer languages. ZPL, EPL, TSPL and DPL all arrive as
text/plain, so the gateway can’t tell them apart from the content type alone. PDF and images
are detected from Content-Type (or sniffed from magic bytes), so sourceFormat is optional
there.
Format conversion is automatic. Send a PDF to a printer configured for ZPL and it’s
converted on the way. Any conversion parameter also works
as a query parameter on this endpoint — rotation=90, dpi=300 — and is applied in transit.
The response is JSON:
{ "jobId": "9f2c…", "status": "dispatched" }dispatched means it reached an online agent. queued means every agent bound to that
printer is offline — the job is held and drains when one reconnects. Treat it as accepted, not
as 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”A bad API key does not fail
Section titled “A bad API key does not fail”The conversion endpoint treats an unresolvable credential as anonymous rather than returning
401. A typo’d key therefore returns a perfectly healthy 200 — carrying a watermarked
free-tier label. Don’t validate your configuration by checking the status code. Convert one
label during setup and look at it.
Test keys and live keys
Section titled “Test keys and live keys”Keys created from the dashboard are always test keys (lz_test_ prefix): they work
everywhere, and they are always watermarked and never billed. Production keys (lz_live_) are
provisioned by sales/support on Pro and above. A Mule app that “works fine but watermarks in
prod” is nearly always this, not a code problem.
Rate limits and connection pooling
Section titled “Rate limits and connection pooling”The gateway allows 100 requests per 60 seconds, counted against your IP and separately
against your credential. The credential bucket keys on the API key string itself — so every
worker in a CloudHub cluster sharing one key shares one bucket, and horizontal scaling does
not buy throughput. Size maxConnections and the concurrency of any <parallel-foreach> or
scatter-gather with that ceiling 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