Published:
Ask the FedEx Ship API for a thermal label and it returns ZPL, base64-encoded, inside the JSON of the shipment response. Which ZPL you get — the label size, whether a doc tab is attached and, above all, which print density the dots are written for — is decided by a handful of request fields. This page lists those fields, shows where the label sits in the response and walks through the checks worth running before the file reaches a printer. The rules that apply to every carrier label (do not edit it, print your own data separately) are in working with carrier ZPL labels; this page is the FedEx-specific layer on top. The UPS counterpart is UPS ZPL labels.
Field names and values below were read from FedEx's published Ship API reference on 2026-09-14. They change between API versions; confirm them against the current reference before you rely on them. Labelixa is not affiliated with FedEx — this is a workflow description, not an integration.
Everything that matters sits in labelSpecification inside
the requested shipment of a POST /ship/v1/shipments call:
imageType — ZPLII for a Zebra data stream.
The other values are EPL2, PDF and
PNG; only the first two are thermal formats.labelFormatType — COMMON2D returns a complete
label. LABEL_DATA_ONLY returns barcode data for a label you
draw yourself and is a different workflow.labelStockType — STOCK_4X6 for plain 4×6 inch
stock. The doc-tab variants (STOCK_4X675_LEADING_DOC_TAB,
STOCK_4X675_TRAILING_DOC_TAB,
STOCK_4X9_LEADING_DOC_TAB,
STOCK_4X9_TRAILING_DOC_TAB,
STOCK_4X85_TRAILING_DOC_TAB,
STOCK_4X105_TRAILING_DOC_TAB) and the tall plain stocks
(STOCK_4X8, STOCK_4X9) are physically longer
than 6 inches — the label you get back is written for that length.resolution — 203 or 300. The
reference states that the default is 203 and that 300 is only allowed
with ZPLII. This one field decides how every coordinate in
the file is to be read.labelRotation (NONE,
LEFT, RIGHT, UPSIDE_DOWN) and
labelPrintingOrientation
(TOP_EDGE_OF_TEXT_FIRST,
BOTTOM_EDGE_OF_TEXT_FIRST) apply to roll-stock thermal
output; labelOrder puts the shipping label first or last
when several documents are produced."labelSpecification": {
"labelFormatType": "COMMON2D",
"imageType": "ZPLII",
"labelStockType": "STOCK_4X6",
"resolution": 203
}
The label is not at the top level. The path is
output.transactionShipments[].pieceResponses[].packageDocuments[]:
one piece response per package, and inside it one or more package
documents, each carrying encodedLabel (the base64 payload)
next to docType, contentType,
trackingNumber and copiesToPrint. A
url field may accompany the document; decoding
encodedLabel yourself keeps the ZPL under your control.
jq -r '.output.transactionShipments[0].pieceResponses[0].packageDocuments[0].encodedLabel' response.json | base64 -d > label.zpl
grep -c '\^XA' label.zpl # number of label formats in the buffer
Count the formats. The reference notes that all labels required for a
shipment are generated and returned in a single buffer, so one decoded
document can contain more than one ^XA … ^XZ block — a
service label after the shipping label, for example. A printer prints all
of them; the preview endpoint returns one format per index, or all of them
as a PDF when the index is left out.
ZPL has no resolution header: the dots in the file are read at the
printer's own density. A file requested with resolution: 300
and previewed at 8 dots/mm looks about one and a half times too large and
runs off the label; the same file at 12 dots/mm is correct. Match the
preview to the request — 8dpmm for 203, 12dpmm
for 300 — and give the stock length you asked for (6,
6.75, 8, 9 …).
curl -X POST "https://api.labelixa.com/v1/printers/8dpmm/labels/4x6/0" --data-binary @label.zpl --output label-0.png
curl -X POST "https://api.labelixa.com/v1/printers/8dpmm/labels/4x6/1" --data-binary @label.zpl --output label-1.png # second format, if any
If you no longer know which resolution a stored file was generated for, the size and DPI analyzer reads the declared width and length out of the file and reports the density that makes them a standard label size — or says that it cannot tell.
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. The reference also names the thermal printers FedEx
recommends for the API — among the ZPL models the GK420, the ZT410 and
other ZT4xx series printers — a useful sanity check when a warehouse
fleet is mixed.
resolution or the printer, not the file.labelRotation or
orientation did it; if only the paper is, the printer's own orientation
setting is the cause — see
label prints upside
down.labelSpecification.resolution — 203 or 300. The reference gives 203 as the default and allows 300 only with imageType ZPLII. Preview at 8 dots/mm for 203 and 12 dots/mm for 300.
In output.transactionShipments[].pieceResponses[].packageDocuments[].encodedLabel, base64-encoded, one piece response per package. One decoded document can hold more than one ^XA…^XZ format, because all labels for a shipment come back in a single buffer.
The file was generated for 203 dpi and printed on a 300 dpi head (or previewed at the wrong density). Change resolution in the request or use a matching printer; do not edit the file.
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.