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.
Turn a spreadsheet into hundreds of ZPL labels: template variables, CSV column mapping, batch generation and the honest limits of each step.
FedEx ZPL labels: request, preview and fixThe Ship API fields that decide the ZPL (image type, stock, 203 or 300 dpi), where the label sits in the response and the checks to run before printing.
ERP label printing with ZPLWhere ERP label pipelines break, and the validation gate that stops a bad template before it reaches a warehouse printer.
Working with carrier ZPL labelsCarrier APIs hand back ZPL, not a picture. How to decode it, check it before it burns media, and what you must not change.