MuleSoft integration
Call the LabelZoom conversion and Cloud Print APIs from a Mule 4 flow with the standard HTTP Request connector. There’s no LabelZoom connector on Anypoint Exchange.
Before you begin
Section titled “Before you begin”- Mule runtime 4.4 or later, and Anypoint Studio 7.x.
- HTTP Connector 1.7 or later, which is bundled with Studio.
- Optional: a LabelZoom API key. Without one, conversions run on the free tier. Printing needs a key with the
printscope; see Authentication.
Configure the connection
Section titled “Configure the connection”To set up a connection that every flow shares, follow these steps:
-
Add an HTTP request config:
<http:request-config name="LabelZoom_Config" responseTimeout="60000"><http:request-connection protocol="HTTPS" host="api.labelzoom.com" port="443"maxConnections="8"/></http:request-config> -
Store the API key in a secure properties file, not in the flow XML:
<secure-properties:config name="Secure_Props" file="config-${mule.env}.yaml"key="${encryption.key}"/>config-prod.yaml labelzoom:apiKey: "![ENCRYPTED_API_KEY]"
Flows read the key with p('secure::labelzoom.apiKey'). Every conversion request follows these rules:
| Setting | Value |
|---|---|
Accept header |
*/* |
Content-Type header |
The source format’s media type, such as text/plain for printer languages or application/pdf for PDF. A mismatch returns 400. See Supported formats. |
requestStreamingMode |
NEVER. Chunked transfer can send a request with no body, which the API rejects. |
outputMimeType |
application/octet-stream, so Mule keeps the response as bytes. |
| Options | One params query parameter that holds JSON, built with DataWeave write(). |
Convert ZPL to PDF
Section titled “Convert ZPL to PDF”This flow takes ZPL from an HTTP listener and writes label.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": "*/*", ("Authorization": "Bearer " ++ apiKey) if (apiKey != '')}]]]></http:headers> </http:request>
<file:write path="label.pdf"/></flow>The response body is the PDF itself. To set options, add a params query parameter to the request:
<http:query-params><![CDATA[#[output application/java---{ "params": write({ dpi: 300, rotation: 90 }, "application/json")}]]]></http:query-params>Omit label to render at the label’s own size. For every option, see Conversion parameters.
Convert a PDF to ZPL
Section titled “Convert a PDF to ZPL”Use the same request shape with the PDF 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": "*/*" }]]]></http:headers></http:request>pdf.pageNumberis 0-based. Omit it to convert every page.pdf.conversionModeisIMAGEby default, which rasterizes each page and keeps its appearance.NATIVEextracts the vector and text content.- If the PDF arrives as a base64 string, send the string as the body with
Content-Type: text/plain.
Write printer-language output as bytes, because decoding it to a String can corrupt labels with graphics; see Binary output.
Fill variable data
Section titled “Fill variable data”Each object in the data array fills one label, so one call turns a record set into a batch of ZPL labels. data is always an array of objects, even for one label. This flow fills a ZPL template that has the merge fields sku, qty, and loc:
<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:input-parameters><![CDATA[#[{ wave: vars.waveId }]]]></db:input-parameters> </db:select>
<set-variable variableName="labelData" value="#[payload]"/> <file:read path="pick-label.zpl"/>
<http:request method="POST" path="/api/v2/convert/zpl/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({ 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": "*/*", ("Authorization": "Bearer " ++ apiKey) if (apiKey != '')}]]]></http:headers> </http:request>
<file:write path="pick-labels.zpl"/></flow>Handle errors
Section titled “Handle errors”By default, the HTTP Request connector throws on any status outside 2xx. To log the API’s error message and request ID, accept every status and branch on it:
<http:request method="POST" path="/api/v2/convert/zpl/to/pdf" config-ref="LabelZoom_Config"> <!-- body, headers, and query-params as in the earlier examples --> <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>For what each status means, see Status codes. If you keep the default throwing behavior, these error types map reliably: HTTP:UNAUTHORIZED, HTTP:FORBIDDEN, HTTP:TOO_MANY_REQUESTS, HTTP:INTERNAL_SERVER_ERROR, HTTP:CONNECTIVITY, and HTTP:TIMEOUT.
Retry only 429, 5xx, and connectivity failures, because a bad request fails the same way every time:
<until-successful maxRetries="3" millisBetweenRetries="1000"> <try> <http:request method="POST" path="/api/v2/convert/zpl/to/pdf" config-ref="LabelZoom_Config"> <!-- body, headers, and query-params as in the earlier examples --> </http:request> <error-handler> <on-error-continue type="HTTP:BAD_REQUEST, HTTP:FORBIDDEN, HTTP:UNAUTHORIZED"/> </error-handler> </try></until-successful>Log the x-lz-request-id response header on every conversion request, and include it when you contact support.
Print a label
Section titled “Print a label”The Cloud Print API sends a document to a printer connected through the LabelZoom Print Agent, so CloudHub doesn’t need network access to the printer:
<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>Idempotency-Keymakes a retry safe: the same key with the same request returns the original job instead of printing again. Reusing a key for a different request returns422. See Idempotency.sourceFormatis required for printer languages, which all arrive astext/plain. PDF and images are detected fromContent-Type.- If the document isn’t in the printer’s native format, LabelZoom converts it in transit. Conversion parameters also work on this endpoint, in the same
paramsquery parameter.
The response is JSON:
{ "jobId": "9f2c4e1a-6b7d-4c3e-8a21-5d0f7b9e2c41", "status": "dispatched", "copies": null }queued also means accepted: no agent is online, and the job prints when one reconnects. To track a job, call GET /api/v3/jobs/{jobId}; see Job statuses. On this path, 403 means the key lacks the print scope or the printer isn’t yours, 404 means the printer doesn’t exist, and 502 means the document couldn’t be converted to the printer’s format.
A key with the print scope can also list printers with GET /api/v3/printers and read printer status and jobs. Manage agents and printers, and retry failed jobs, on the Printers page.
Print a template
Section titled “Print a template”To print a template designed in LabelZoom Studio, send its values as a JSON object:
<http:request method="POST" path="/api/v3/printers/{printerId}/templates/{templateId}/print" config-ref="LabelZoom_Config" requestStreamingMode="NEVER"> <http:body><![CDATA[#[output application/json---{ orderNumber: payload.orderNumber, sku: payload.sku }]]]></http:body> <http:uri-params><![CDATA[#[{ printerId: p('labelzoom.printerId'), templateId: p('labelzoom.templateId') }]]]></http:uri-params> <http:query-params><![CDATA[#[{ validate: 'true' }]]]></http:query-params> <http:headers><![CDATA[#[output application/java---{ "Content-Type": "application/json", "Accept": "*/*", "Authorization": "Bearer " ++ p('secure::labelzoom.apiKey'), "Idempotency-Key": correlationId}]]]></http:headers></http:request>The response is 202 with the same job fields as a raw print. With validate=true, a missing merge field returns 422 instead of printing the text null. To find template IDs, call GET /api/v3/templates. To print several labels in one request, send an array of objects; see Templates.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Fix |
|---|---|
400 because the content type doesn’t match |
Send the source format’s media type in Content-Type, such as text/plain for ZPL. |
400 because the request body is missing |
Set requestStreamingMode="NEVER" on the request. |
406 |
Accept names a media type the API never returns. Send Accept: */*. |
| The output has a watermark | The key is missing or invalid, or the account has no paid plan; see Authentication troubleshooting. |
422 with idempotency_key_reused |
Send a new Idempotency-Key for each different print request. |
429 |
Wait for the number of seconds in Retry-After, and then retry; see Rate limits. |
Production checklist
Section titled “Production checklist”- Use a live key, and keep it in secure properties or Anypoint Secrets Manager; see Authentication.
- A bad key converts anonymously. Check your first output for a watermark; see Authentication troubleshooting.
- Every CloudHub worker that uses a key shares its rate limit, so size
maxConnectionsand parallel processing for one budget; see Rate limits. - Retry only
429,5xx, and connectivity failures; see Retry failed requests. - Send a new
Idempotency-Keyfor each deliberate reprint; see Idempotency.
What’s next
Section titled “What’s next”- Conversion parameters
- Cloud Print API
- Errors
- Java quickstart, to call the Java SDK from a custom component
- Swagger UI