Skip to main content

LabelZoom Logo

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)

Source distribution for labelzoom-sdk 1.1.0
File Size Uploaded Provenance
labelzoom_sdk-1.1.0.tar.gz 17.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for labelzoom-sdk 1.1.0
File Interpreter ABI Platform
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page