LabelZoom Python SDK
Official Python client for the LabelZoom API. Converts barcode labels between ZPL, EPL, IPL, TSPL, DPL, SBPL, PDF, LabelZoom XML/JSON, and raster images.
Python 3.10+. Fully typed (py.typed), sync and async clients, one dependency
(httpx).
Install
pip install labelzoom-sdk
The distribution is labelzoom-sdk; the import is labelzoom.
Stable. The public API is covered by a shared conformance suite that every language SDK runs against the same fixtures. Each SDK is versioned independently, and the contract carries its own version in
conformance/spec.json.
Quick start
An API key is optional. Without one you get the free tier — watermarked output, first label only, a 1 MB request cap, and no multi-page, JSON-target, or image-to-image conversion.
from pathlib import Path
from labelzoom import LabelZoomClient
with LabelZoomClient() as client: # anonymous; this works
result = client.convert(
"zpl", "png",
"^XA^FO20,20^A0N,28^FDHello^FS^XZ",
dpi=300, label_width=4, label_height=6,
)
Path("label.png").write_bytes(result.content)
result.content is the authoritative payload — the PDF and image targets are binary, and every
printer-language target can inline binary of its own.
result.text decodes it using the response charset for the textual ones.
With a key — passed explicitly, or picked up from LABELZOOM_API_KEY:
client = LabelZoomClient("lz_live_...")
from_env = LabelZoomClient() # reads LABELZOOM_API_KEY
anonymous = LabelZoomClient(None) # forces anonymous, ignoring the env
Omitting the argument and passing None mean different things on purpose: omitting it consults
the environment, and None (or "") suppresses that fallback.
Filling variable fields — each record produces one label:
result = client.convert(
"zpl", "pdf", template,
data=[{"name": "ACME Corp", "sku": "12345"},
{"name": "Globex", "sku": "67890"}],
) # a 2-page PDF
Async
AsyncLabelZoomClient is a mirror, not a wrapper — same arguments, same behaviour, awaited.
The test suite asserts the two signatures are identical, so they cannot drift.
import asyncio
from labelzoom import AsyncLabelZoomClient
async def main() -> None:
async with AsyncLabelZoomClient() as client:
result = await client.convert("zpl", "png", zpl, dpi=300)
print(result.status, result.content_type, len(result.content))
asyncio.run(main())
Formats
SourceFormat = Literal["zpl", "epl", "ipl", "tspl", "dpl", "sbpl",
"xml", "json", "pdf", "png", "bmp", "gif",
"jpeg", "jpg", "url"]
TargetFormat = Literal["zpl", "epl", "ipl", "tspl", "dpl", "sbpl",
"xml", "json", "pdf", "png", "bmp", "gif",
"jpeg"]
jpg and url are source-only — jpg normalizes to jpeg, and url is a fetch instruction
rather than a format — and the type system says so:
client.convert("pdf", "url", body)
# ^^^^^ error: Argument 2 has incompatible type "Literal['url']";
# expected "Literal['zpl', 'epl', 'ipl', 'tspl', ...]"
A mypy error, not a runtime 404. Run mypy and you find it before you ship; the SDK also raises locally rather than round-tripping to the server.
epl, ipl, tspl, dpl and sbpl are targets as well as sources — client.convert("pdf", "epl", body) is a real conversion. Their
output is text/plain with every label concatenated, but they can all carry raw binary (EPL's GW,
TSPL's BITMAP, IPL's STX/ETX-framed bitmap columns, SBPL's inline graphics): read result.content rather than result.text whenever a label might carry graphics.
"url" as the source has the server fetch a URL you supply and convert what it finds.
Validate the URL first if it came from untrusted input.
Options
Options are keyword arguments with the API's nesting flattened by underscores — label.width
becomes label_width, pdf.pageNumber becomes pdf_page_number.
| Keyword | Notes |
|---|---|
dpi |
server default 203 |
rotation |
must be a multiple of 90; rejected locally otherwise |
scaling |
percent, server default 100 |
color_mode |
"BW", "GRAYSCALE" (default), "COLOR" |
darkness |
0–100, server default 70 |
position_x, position_y |
pixel offset of the extracted region |
watermark |
forced on for the free tier regardless |
dialect |
e.g. "moca"; paid |
label_width, label_height |
inches, not dots |
pdf_conversion_mode |
"IMAGE" (default) or "NATIVE" |
pdf_page_number |
0-based; omit to convert every page |
zpl_commands_to_ignore |
e.g. ["^PQ"] |
zpl_image_compression |
"Z64" (default) or "COMPRESSED_HEX" |
data |
one label per record |
extra |
anything not modeled yet; merged in as-is |
Only options you actually set are sent. The SDK never fills in a client-side default, so a
change to a server default reaches you without an SDK upgrade. That is why unset options use an
UNSET sentinel rather than None — watermark=False is a value you can deliberately send.
For building a request programmatically, ConversionOptions is the same field set as a frozen
dataclass:
from labelzoom import ConversionOptions
preset = ConversionOptions(dpi=300, label_width=4, label_height=6)
result = client.convert("zpl", "png", zpl, options=preset, rotation=90)
Keyword arguments passed alongside options win.
Errors
Every non-2xx raises a typed error carrying the server's own message, the raw body, and the
X-LZ-Request-Id support handle. The body is never discarded.
from labelzoom import ForbiddenError, LabelZoomError
try:
client.convert("zpl", "json", zpl)
except ForbiddenError as error:
if error.is_paid_feature:
# "JSON export is a paid feature" — the most common free-tier failure.
print(f"{error.message} (request {error.request_id})")
except LabelZoomError as error:
print(f"{error.status}: {error.message}")
BadRequestError, UnauthorizedError, ForbiddenError, NotFoundError,
PayloadTooLargeError, RateLimitedError and ServerError all subclass LabelZoomError.
LabelZoomValidationError deliberately does not — it means the calling code is wrong, it
never reaches the network, and it should not be swallowed by a handler written for server
failures. It subclasses ValueError.
Retries
429, 5xx and transport failures are retried automatically: 3 attempts, 1s/2s/4s with full
jitter, honouring a longer Retry-After. Other 4xx responses raise immediately — a malformed
request will not become valid on a second attempt.
client = LabelZoomClient(max_retries=0, timeout=30.0)
Testing your own code
The transport and the sleep function are both injectable, so retry logic can be tested without spending the wall-clock time:
import httpx
from labelzoom import LabelZoomClient
slept: list[float] = []
client = LabelZoomClient(
None,
transport=httpx.MockTransport(lambda request: httpx.Response(200, text="^XA^XZ")),
use_jitter=False,
sleep=slept.append,
)
Pass http_client=httpx.Client(...) instead to share a connection pool with the rest of your
application. The SDK will not close a client it did not create.
Development
pip install -e ".[dev]"
pytest # offline; no key, no network
mypy
ruff check
The test suite runs the shared conformance fixtures that every LabelZoom SDK is checked against, executes each one through both the sync and async clients, and asserts it ran all of them. See docs/CONFORMANCE.md.
License
MIT — see LICENSE.
Metadata
Release files for labelzoom-sdk 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| labelzoom_sdk-1.1.0.tar.gz | 17.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| labelzoom_sdk-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 37.6 kB
Release files / labelzoom_sdk-1.1.0.tar.gz
| Download URL | labelzoom_sdk-1.1.0.tar.gz |
|---|---|
| Size | 17.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c811747a1edf0d1f50926f502fe0788708eff257862d603c6bbab7bdb5d395ae
|
|
BLAKE2b-256 checksum How to use checksums |
31fa9beb93d93fe537deb48f908d6696dc6837cde4c3b31eeda2d0aec793919c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.
Transparency logRelease files / labelzoom_sdk-1.1.0-py3-none-any.whl
| Download URL | labelzoom_sdk-1.1.0-py3-none-any.whl |
|---|---|
| Size | 19.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
27a8cac3640d6afb5c11e1bc29c1c20849f3591df7fdb4e6de752448232638c3
|
|
BLAKE2b-256 checksum How to use checksums |
700545f14e30de2237bfdc5f037adcc22fa79ac1ee32eb1c9ae4c36be0466f61
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.
Transparency logSecurity for labelzoom-sdk 1.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for labelzoom_sdk-1.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.