コンテンツに移動

Migrate from the Labelary API to OpenZPL

To move a Labelary v1 client to OpenZPL, change the host from api.labelary.com to api.openzpl.com and keep the paths, headers and request bodies. Then check the few status codes and limits that differ, listed below with the tests we ran.

2026-10-20 に更新されました · Renders made with the OpenZPL engine

Open in the viewer
OpenZPL is an independent project. It is not affiliated with, endorsed by or connected to Labelary or Zebra Technologies; the names are used only to identify the services compared.

1. Change the host

The render path is the same, so only the base URL changes:

- https://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/
+ https://api.openzpl.com/v1/printers/8dpmm/labels/4x6/0/
curl https://api.openzpl.com/v1/printers/8dpmm/labels/4x6/0/ \
  --data-binary @label.zpl > label.png

curl https://api.openzpl.com/v1/printers/8dpmm/labels/4x6/ \
  -H "Accept: application/pdf" --data-binary @labels.zpl > labels.pdf

In Python, keep the base URL in one setting so you can switch back:

import os
import requests

BASE = os.environ.get("ZPL_API", "https://api.openzpl.com")  # was https://api.labelary.com


def render_pdf(zpl: str, size: str = "4x6", dpmm: int = 8) -> bytes:
    res = requests.post(
        f"{BASE}/v1/printers/{dpmm}dpmm/labels/{size}/",
        data=zpl.encode("utf-8"),
        headers={"Accept": "application/pdf"},
        timeout=15,
    )
    res.raise_for_status()
    return res.content

2. Keep your headers

Accept (image/png or application/pdf), X-Quality and X-Rotation work as they do on Labelary, and the response carries X-Total-Count. X-Page-Size, X-Page-Layout, X-Label-Border and X-Linter aren't supported: OpenZPL returns one label per page at the label's own size. Other Accept values such as application/epl get 406.

3. Add a key if you need more than the free plan

The free plan needs no key and is limited per IP. With a key from your dashboard, send it in X-API-Key and your workspace's limits apply. A Labelary private hostname has no equivalent: everyone uses api.openzpl.com.

curl https://api.openzpl.com/v1/printers/8dpmm/labels/4x6/0/ \
  -H "X-API-Key: oz_live_…" --data-binary @label.zpl > label.png

4. Recount your limits

Labelary's daily quota counts requests; OpenZPL's counts labels. If your requests return multi-label PDFs, divide your daily requests by the labels per request to see what fits in 5,000 labels, or in your plan on the pricing page.

5. Handle 429 and 503

On 429, wait the seconds in Retry-After: 1 for the per-second limit, until midnight UTC for the daily one. On 503 (a label over 10 seconds of render time, or the service at capacity), retry once with a short delay.

import time


def post_with_retry(url, body, headers, attempts=3):
    for _ in range(attempts):
        res = requests.post(url, data=body, headers=headers, timeout=15)
        if res.status_code == 429:
            wait = int(res.headers.get("Retry-After", "1"))
            if wait > 60:
                res.raise_for_status()  # daily allowance used up
            time.sleep(wait)
            continue
        if res.status_code == 503:
            time.sleep(1)
            continue
        res.raise_for_status()
        return res
    res.raise_for_status()

What we tested

Tested 2026-10-20 with synthetic labels, the same request sent to api.labelary.com (free tier) and to the OpenZPL engine. We compared status codes, headers, page counts and image size, not pixels.

リクエストOpenZPLLabelary
PNG, one label, 4 × 6 in at 8 dpmm200, 812 × 1218 px200, 812 × 1218 px
PNG at 12 dpmm200, 1216 × 1824 px200, 1216 × 1824 px
PDF, one label200, 1 page200, 1 page
PDF, no index, 2 formats with ^PQ2 on the second200, 3 pages, X-Total-Count: 3200, 3 pages, X-Total-Count: 3
PNG, index 2 of 3200200
Index 5 of 1404, X-Total-Count: 1404, X-Total-Count: 1
^XA^XZ with no fields404, X-Total-Count: 0404, X-Total-Count: 0
Accept: application/x-nope406406
Accept: application/epl406200, EPL
X-Rotation: 90200, 1218 × 812 px200, 1218 × 812 px
X-Quality: Bitonal200, 1-bit PNG200, 1-bit PNG
^GFA image, 32 × 32 dots200, same position200, same position
^CI28 with accented text200200
Label 16 in wide400400

Differences to watch

  • Output formats other than PNG and PDF answer 406; keep those calls on Labelary or drop them.
  • Pixels can differ where fonts differ. Compare images visually or by the position of key fields, not byte by byte.
  • Error texts differ. Both start with ERROR:; don't match on the rest.
  • Commands the engine doesn't support yet print differently or not at all; see compatibility.

Rolling back

Because nothing else changes, rolling back is setting the base URL to api.labelary.com again. Keep it in configuration, not in code, and switch a share of traffic first: render a day of real labels on both, compare a sample, then move the rest.

質問

Do I have to change my ZPL?

No. The ZPL goes in the body or the URL exactly as before.

Does OpenZPL keep my labels?

No. The API renders the label and returns it; the ZPL isn't stored. API key requests are counted for the usage dashboard without their content.

Sources