zpljet
Official Python SDK for the ZPLJet API — fast ZPL → PDF/PNG conversion.
- Zero dependencies — a single small client on top of the stdlib
- Fully typed (
py.typed) — parameters, results, and every API error code - Reliable by default — automatic retries with exponential backoff (honoring
Retry-After), per-request timeouts, typed exceptions - Sync and async —
ZplJetfor scripts and servers,AsyncZplJetfor asyncio code - Python ≥ 3.10; tested through 3.14
Installation
Choose one:
pip install zpljet
uv add zpljet
poetry add zpljet
Quickstart
Create an API key in the dashboard (keys look like zpl_…), then:
import os
from pathlib import Path
from zpljet import ZplJet
zpljet = ZplJet(api_key=os.environ["ZPLJET_API_KEY"])
label = zpljet.convert(zpl="^XA^FO50,50^A0N,50,50^FDHello^FS^XZ")
Path("label.pdf").write_bytes(label.data)
Keep your API key server-side. Anyone with the key can spend your quota.
Usage
Convert to PDF or PNG
convert() accepts every parameter of POST /v1/convert:
label = zpljet.convert(
zpl="^XA^FO50,50^A0N,50,50^FDHello^FS^XZ",
format="png", # "pdf" (default) | "png"
dpmm=12, # 6 | 8 (default, 203 dpi) | 12 (300 dpi) | 24 (600 dpi)
width_mm=101.6, # label width, default 4 in
height_mm=152.4, # label height, default 6 in
)
label.data # bytes — the file
label.content_type # "application/pdf" | "image/png"
label.id # conversion id (shows up in your dashboard)
Hosted URLs (paid plans)
Pass output="url" to have ZPLJet host the file and return a public link
instead of the bytes. Files are retained for your account's retention window
(a dashboard setting, up to your plan's maximum).
hosted = zpljet.convert(zpl="^XA^FO50,50^A0N,50,50^FDHello^FS^XZ", output="url")
hosted.url # public URL to the PDF (works until the file is deleted)
hosted.pages # pages rendered (one per ^XA…^XZ block)
hosted.retention_days # how long the file is kept
hosted.expires_at # when the file is deleted and the URL stops working (ISO 8601, UTC)
The return type narrows automatically: output="url" gives a HostedLabel,
everything else a LabelData.
Async
AsyncZplJet has the identical interface for asyncio code:
from zpljet import AsyncZplJet
zpljet = AsyncZplJet(api_key=os.environ["ZPLJET_API_KEY"])
label = await zpljet.convert(zpl="^XA^FO50,50^A0N,50,50^FDHello^FS^XZ")
The async client runs the dependency-free transport in a worker thread. Bound
large batches with a semaphore; see
examples/05_async_batch.py.
Error handling
Every API error code maps to a dedicated exception, so you branch with
except — no string matching:
from zpljet import (
ZplJet,
APIConnectionError,
BadRequestError,
ConversionFailedError,
QuotaExceededError,
RateLimitError,
)
try:
label = zpljet.convert(zpl=zpl)
except BadRequestError as err:
print(f"Invalid request ({err.param}): {err.message}")
except QuotaExceededError as err:
print(f"Quota used up ({err.used}/{err.quota}), resets {err.resets_at}")
except RateLimitError as err:
print(f"Rate limited — retry after {err.retry_after}s")
except ConversionFailedError as err:
print(f"Engine rejected the ZPL (conversion {err.conversion_id})")
except APIConnectionError as err:
print(f"Network problem: {err}")
| Exception | Status | error.code |
Extra fields |
|---|---|---|---|
BadRequestError |
400 | invalid_request |
param |
AuthenticationError |
401 | missing_api_key · invalid_api_key |
— |
QuotaExceededError |
402 | quota_exceeded |
plan, quota, used, resets_at |
PermissionDeniedError |
403 | hosting_not_allowed · no_retention_enforced |
— |
PayloadTooLargeError |
413 | payload_too_large |
— |
RateLimitError |
429 | rate_limit_exceeded |
retry_after, retry_at |
ConversionFailedError |
502 | conversion_failed |
conversion_id |
ServiceUnavailableError |
503 | service_unavailable |
retry_after |
APIError |
any | anything else | status, code, raw |
APITimeoutError |
— | (an attempt timed out) | — |
APIConnectionError |
— | (request never got a response) | — |
All of these extend ZplJetError, and every HTTP error carries status,
code, doc_url, and the raw error payload in raw. Full code reference:
zpljet.com/docs/errors.
Retries
Rate limits, transient 5xx responses, timeouts, and network failures retry up
to twice by default. Retries use exponential backoff and honor Retry-After.
conversion_failed is never retried.
# Client-wide
zpljet = ZplJet(api_key=key, max_retries=5)
# Or per request
zpljet.convert(zpl=zpl, max_retries=0) # fail fast
Timeouts
Each attempt has a 60-second timeout by default:
zpljet = ZplJet(api_key=key, timeout=10.0)
# Per request:
zpljet.convert(zpl=zpl, timeout=5.0)
A timed-out attempt raises APITimeoutError (after retries).
Configuration
zpljet = ZplJet(
api_key="zpl_…", # required
base_url="https://api.zpljet.com", # default
timeout=60.0, # per-attempt timeout, seconds
max_retries=2, # automatic retries
transport=my_transport, # custom transport (proxies, tests)
)
The default transport uses stdlib urllib. For connection pooling, inject any
callable matching (url, body, headers, timeout) -> TransportResponse.
Examples
Runnable scripts live in examples/:
ZPLJET_API_KEY=zpl_… python examples/01_convert_to_pdf.py
Contributing & development
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
ruff check .
mypy
pytest
ZPLJET_API_KEY=zpl_… pytest tests/test_e2e.py
License
Metadata
Release files for zpljet 2.0.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 | |
|---|---|---|---|
| zpljet-2.0.0.tar.gz | 18.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| zpljet-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.6 kB
Release files / zpljet-2.0.0.tar.gz
| Download URL | zpljet-2.0.0.tar.gz |
|---|---|
| Size | 18.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9b4621441a7c4f7a8138dd8734601ae10f996ba262bf0a4c2235879565feed9c
|
|
BLAKE2b-256 checksum How to use checksums |
ed3cbad2b557882265c696b79cf9c62564a127c9ecc7e138000305a419b4302d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
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 Jul 17, 2026.
Transparency logRelease files / zpljet-2.0.0-py3-none-any.whl
| Download URL | zpljet-2.0.0-py3-none-any.whl |
|---|---|
| Size | 12.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0f52155cae1235feb42ef2d4081cf9ac74ae00b5f107cbd015f73cc2a3328ad7
|
|
BLAKE2b-256 checksum How to use checksums |
d6514dc073c24c3cfc342d28ec863a955f5a722ee061fb8738027fe343e696c8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
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 Jul 17, 2026.
Transparency log