Published:
The UPS Shipping API returns a thermal label as base64 ZPL, one per package, when the request asks for it. Compared with FedEx the request is smaller — an image format code and a stock size — and there is no resolution field at all, which moves one important question (which density are the dots written for?) from the request to the file. This page lists the fields, the path to the label in the response and the checks to run before printing. The rules that apply to every carrier label are in working with carrier ZPL labels; the FedEx counterpart is FedEx ZPL labels.
Field names and values below were read from the UPS Shipping API OpenAPI specification that UPS publishes on GitHub, on 2026-09-14. They change between API versions; confirm them against the current specification before you rely on them. Labelixa is not affiliated with UPS — this is a workflow description, not an integration.
The call is POST /shipments/{version}/ship — against
wwwcie.ups.com for the Customer Integration Environment and
onlinetools.ups.com for production. The label is controlled
by ShipmentRequest.Shipment.LabelSpecification:
LabelImageFormat.Code — ZPL. The
specification lists GIF, ZPL, EPL
(for EPL2) and SPL; it notes that shipments with the print
return label service accept the same four, and that UPS Premier Silver
shipments support ZPL only.LabelStockSize.Height and Width — whole
inches, required for thermal formats. Width is 4; height is
6 or 8. The specification adds a note worth
reading twice: the label image only scales up to 4×6, even when 4×8 is
requested.CharacterSet — the language character set expected on
the label, as a three-letter code: eng (Latin-1, the
default), deu and fra (Latin-1),
tur (Latin-5), pol (Latin-2), rus
(Cyrillic) and others. It changes the bytes you get back, which matters
below.HTTPUserAgent — only relevant for GIF output; ignore it
for ZPL."LabelSpecification": {
"LabelImageFormat": { "Code": "ZPL" },
"LabelStockSize": { "Height": "6", "Width": "4" }
}
There is no resolution field. The specification does not say which density the ZPL is written for, so do not assume one: read it from the file (below).
The path is
ShipmentResponse.ShipmentResults.PackageResults[] — one entry
per package, each with its TrackingNumber and a
ShippingLabel object whose GraphicImage is the
base64 label and whose ImageFormat.Code confirms the format
actually returned. The specification notes that from version
v2403 package results are always an array, while older
versions return a bare object when there is a single package — a classic
cause of "works with one parcel, breaks with two".
jq -r '.ShipmentResponse.ShipmentResults.PackageResults[0].ShippingLabel.GraphicImage' response.json | base64 -d > label.zpl
head -c 40 label.zpl # ^XA expected
Two more fields matter for thermal output.
InternationalSignatureGraphicImage is returned for non-US
shipments in EPL2, ZPL and SPL — one image per shipment, in the first
package result — and is a second label to print, not an attachment to
ignore. HTMLImage is only returned for GIF and PNG, so its
absence with ZPL is normal. The specification also notes that for
multi-piece COD shipments the first package's label is always GIF,
whatever format was requested — check ImageFormat.Code per
package instead of assuming.
Because the request carries no resolution, the only reliable source is the file. The size and DPI analyzer reads the declared width and length out of the ZPL and reports the density that makes them a 4×6 (or 4×8) label; if the file does not declare them, it says so rather than guessing. Then preview at that density and stock:
curl -X POST "https://api.labelixa.com/v1/printers/8dpmm/labels/4x6/0" --data-binary @label.zpl --output label.png
Keep the decoded bytes exactly as they are. If you requested a character set other than the default, the label bytes follow that encoding; decoding the file as UTF-8 and re-encoding it can replace or reject every accented or Turkish character, and the preview will show precisely that damage. Feed the raw bytes to the preview and, unchanged, to the printer.
ImageFormat.Code; a GIF that slips into a ZPL queue makes a
printer print garbage or nothing.curl -X POST "https://api.labelixa.com/v1/diagnostics?dpmm=8&w=4&h=6" --data-binary @label.zpl
The same gate runs from a shell or CI as
npx labelixa validate label.zpl, which exits non-zero on
error-level findings.
CharacterSet in the request and
keep the payload binary.v2403.Set LabelSpecification.LabelImageFormat.Code to ZPL and give LabelStockSize with Width 4 and Height 6 (or 8) in whole inches. The specification notes that the image only scales up to 4×6 even when 4×8 is requested.
ShipmentResponse.ShipmentResults.PackageResults[].ShippingLabel.GraphicImage, base64-encoded, one entry per package; ImageFormat.Code confirms the format. From v2403 the package results are always an array.
No — the specification has no resolution field. Read the density from the file with the size and DPI analyzer, then preview and print at that density.
ZPL is the language Zebra thermal printers understand. How to read the commands, how a label is put together, and the most common mistakes.
Which barcode symbology should I use?EAN-13, Code 128 or QR? Practical rules for picking a barcode based on your data, your space and the scanner that has to read it.
How to preview a ZPL file without a printerA ZPL file is just text — the label only exists once something draws it. Three practical ways to see a ZPL label before it reaches the printer.