caretly
Python client for the Caretly API: render ZPL labels to PDF or PNG.
- Sync (
caretly.Client) and asyncio (caretly.AsyncClient) clients with the same API. - One dependency:
httpx. - Typed (
py.typed), Python 3.9+. - Retries rate limits (429), overload (503) and connection errors, with an
Idempotency-Keyso a retry is never billed twice.
Install
pip install caretly
Quickstart
Create an API key in the console. Live keys start
with zpl_live_, test keys with zpl_test_ (test keys are free and watermark every label).
import caretly
client = caretly.Client(api_key="zpl_test_...") # or set CARETLY_API_KEY
result = client.render("^XA^FO50,50^A0N,50,50^FDHello^FS^XZ")
result.save("label.pdf")
print(result.page_count, result.labels_billed)
png = client.render("^XA^FO50,50^A0N,50,50^FDHello^FS^XZ", format="png", dpmm=12)
png.save("label.png")
Several files in one request, each with a fresh printer state, pages in upload order:
result = client.render_files({"order-1.zpl": zpl_1, "order-2.zpl": zpl_2})
print(result.file_page_counts) # (2, 1)
files is a mapping of name to content, or a sequence of (name, content) pairs.
Content is str (sent as UTF-8) or bytes.
Async:
import asyncio
import caretly
async def main() -> None:
async with caretly.AsyncClient() as client:
result = await client.render("^XA^FDHello^FS^XZ")
result.save("label.pdf")
asyncio.run(main())
Close the client when done (client.close() / await client.aclose()), or use it as a
(async) context manager.
Client options
| Argument | Default | Meaning |
|---|---|---|
api_key |
$CARETLY_API_KEY |
Sent as Authorization: Bearer. Optional for self-hosted servers. |
base_url |
https://caretly.dev |
Server URL. |
timeout |
30.0 |
Seconds per request, an httpx.Timeout, or None for none. |
max_retries |
2 |
Retries for 429, 503 and connection errors. |
http_client |
new client | Your own httpx.Client / httpx.AsyncClient (proxies, transports, ...). You keep ownership: closing the Caretly client does not close it. |
Render options
render(zpl, ...) and render_files(files, ...) take these keyword arguments:
| Option | Default | Meaning |
|---|---|---|
format |
"pdf" |
"pdf" holds every page; "png" is the single page chosen with page. |
page |
server: 1 | For PNG, the page to render, starting at 1. Counts across all files. |
dpmm |
server: 8 | Printer resolution in dots per mm: 6, 8, 12 or 24 (152-600 dpi). |
timings |
False |
Ask for a Server-Timing header (result.server_timing). |
idempotency_key |
generated | Up to 255 characters, e.g. an order number. A retry with the same key and labels is billed once. When unset and max_retries > 0, a UUID is generated per call. |
compress |
False |
Gzip the request body. Useful for large batches. |
page and dpmm are checked before sending and raise ValueError when out of range.
render_pdf(zpl, ...) and render_png(zpl, page=None, ...) are shorthands.
Result
RenderResult is a frozen dataclass:
| Field | Type | Meaning |
|---|---|---|
content |
bytes |
The PDF or PNG. |
content_type |
str |
application/pdf or image/png. |
page_count |
int |
Total pages across the input. |
file_page_counts |
tuple[int, ...] |
render_files only: pages per file; else (). |
labels_billed |
int | None |
Labels billed (0 for test keys); None without keys. |
rate_limit |
RateLimit | None |
limit (burst size) and remaining. |
server_timing |
str | None |
Raw Server-Timing header, with timings=True. |
result.save(path) writes content to a file and returns the pathlib.Path.
client.health() returns True when GET /health answers ok, False for any other
response, and raises APIConnectionError when the server cannot be reached.
Errors
All errors derive from caretly.CaretlyError.
| Exception | When |
|---|---|
BadRequestError |
400: ZPL parse error, invalid parameter, page out of range. |
AuthenticationError |
401: API key missing, not valid, expired or revoked. |
PaymentRequiredError |
402: billing inactive; live keys paused (test keys keep working). |
PayloadTooLargeError |
413: body over 100 MiB after decompression. |
UnsupportedEncodingError |
415: unsupported Content-Encoding. |
RateLimitError |
429: too many requests for this key. |
OverloadedError |
503: the server is shedding load (not billed). |
APIError |
Base class of the above, and any other status (e.g. 500). |
APIConnectionError |
No response: network or protocol failure. |
APITimeoutError |
The request timed out (a subclass of APIConnectionError). |
APIError has status_code, message (the server's plain-text explanation),
retry_after (seconds, or None) and response (the httpx.Response).
try:
client.render(zpl)
except caretly.BadRequestError as e:
print("Fix the label:", e.message)
except caretly.APIError as e:
print(e.status_code, e.message)
Retries
429 and 503 responses and connection errors are retried up to max_retries times
(default 2). Timeouts are not retried: APITimeoutError is raised on the first one. The client waits Retry-After seconds when the server
sends it, else 0.5 s, 1 s, 2 s, ... up to 8 s. Other statuses are not retried. Set
max_retries=0 to turn retries off.
Development
python -m venv .venv
.venv/bin/pip install -e . --group dev # pip >= 25.1
.venv/bin/pytest
.venv/bin/mypy --strict src
.venv/bin/ruff check . && .venv/bin/ruff format --check .
Publishing
-
Bump
__version__insrc/caretly/_version.pyand add aCHANGELOG.mdentry. -
Build and check:
rm -rf dist .venv/bin/python -m build .venv/bin/twine check dist/*
-
Optionally try TestPyPI first:
.venv/bin/twine upload --repository testpypi dist/*. -
Upload:
.venv/bin/twine upload dist/*(with a PyPI API token as password, user__token__), or set up trusted publishing. -
Tag the release, e.g.
git tag clients/python/v0.1.0 && git push origin clients/python/v0.1.0.
Links
- Caretly: https://caretly.dev
- Source: https://github.com/jaapieaapie1/zpl (this client lives in
clients/python)
License
MIT, see LICENSE.
Metadata
Release files for caretly 0.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 | Actions |
|---|---|---|---|---|
| caretly-0.1.0.tar.gz | 15.7 kB | With provenance | Details for caretly-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| caretly-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for caretly-0.1.0-py3-none-any.whl |
Total release size: 31.6 kB
Release files / caretly-0.1.0.tar.gz
| Download URL | caretly-0.1.0.tar.gz |
|---|---|
| Size | 15.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
af5d4311aaadd496b97eecf67b8a6f0a9f9bd1123b1c9ac38383673f53346ab7
|
|
BLAKE2b-256 checksum How to use checksums |
047f6fd41abd8eb17e14095187c279efe678756211587013d43f91ba2b7ec1b6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 30, 2026.
Transparency logRelease files / caretly-0.1.0-py3-none-any.whl
| Download URL | caretly-0.1.0-py3-none-any.whl |
|---|---|
| Size | 15.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
31f247da33c0e9143e20401ef3848ffac22f891fef0db5cdc319168de0a61a07
|
|
BLAKE2b-256 checksum How to use checksums |
43a3a1c970a74a036c966eb3eca9f26d0f5acd0ec603ef3c8309a9805d37f15a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 30, 2026.
Transparency logSecurity for caretly 0.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 caretly-0.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 Oct 30, 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.
To learn more about protecting yourself from malicious actors, visit our package security documentation.