# OpenZPL API

Render ZPL (Zebra Programming Language) labels to PNG or PDF over HTTP. Compatible with Labelary's v1 API: same paths, headers and `X-Total-Count`; change the host.

Base URL: `https://api.openzpl.com`. OpenAPI: https://openzpl.com/openapi.json. HTML docs: https://openzpl.com/docs/api/.

## Render a label

```
POST https://api.openzpl.com/v1/printers/{dpmm}/labels/{width}x{height}/{index}/
POST https://api.openzpl.com/v1/printers/{dpmm}/labels/{width}x{height}/      # PDF with every label
GET  https://api.openzpl.com/v1/printers/{dpmm}/labels/{width}x{height}/{index}/{url-encoded zpl}
```

- `dpmm`: print density, one of `6dpmm` (152 dpi), `8dpmm` (203 dpi), `12dpmm` (300 dpi), `24dpmm` (600 dpi).
- `width`, `height`: label size in inches, e.g. `4x6` or `2.25x1.25`, up to 15.
- `index`: which label to render, from 0. Leave it out with `Accept: application/pdf` to get every label, one per page.
- Body: the raw ZPL, or a multipart form field named `file`.

## Headers

| Header | Values | Default |
|---|---|---|
| `Accept` | `image/png`, `application/pdf` | PNG |
| `X-Quality` | `Grayscale` (8-bit, smooth edges) or `Bitonal` (1-bit, smaller) | Grayscale |
| `X-Rotation` | `0`, `90`, `180`, `270` (clockwise) | 0 |
| `X-API-Key` | your workspace key (optional) | none |

Responses carry `X-Total-Count`: the labels the ZPL produces, `^PQ` copies included.

## Limits

- Free, no sign-up and no key: 3 requests per second and 5,000 labels per UTC day, per IP address.
- Labels, not requests, count against the daily allowance: every label rendered, `^PQ` copies and PDF pages included. A 10-label PDF is 1 request and 10 labels.
- Every request: up to 50 labels, a 1 MB body, 15 inches per side, and 10 seconds of render time.
- Paid plans (Starter $9/month: 10 requests/s and 25,000 labels/day, Pro $29/month: 10 requests/s and 100,000 labels/day, Business $99/month: 20 requests/s and 500,000 labels/day) are announced but billing isn't live yet. API keys work today; a workspace stays on the Free plan until billing opens.
- Over a limit the API answers `429` with `Retry-After` (seconds) and a body that names the limit, e.g. `ERROR: Daily limit reached: 5000 labels per UTC day on this plan (^PQ copies and PDF pages count)`.

## Errors

Errors are `text/plain` and start with `ERROR: `.

| Status | Meaning |
|---|---|
| 400 | Invalid parameter, label too large, index out of range, or no labels in the ZPL |
| 401 | Unknown or revoked API key |
| 406 | `Accept` asks for a format that isn't available |
| 413 | Body over 1 MB, or more than 50 labels |
| 429 | Over a limit; wait `Retry-After` seconds |
| 503 | Render took over 10 s, or the service is at capacity; retry |

## Examples

```sh
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
```

```python
import requests

res = requests.post("https://api.openzpl.com/v1/printers/8dpmm/labels/4x6/", data=open("labels.zpl", "rb"),
                    headers={"Accept": "application/pdf"}, timeout=30)
res.raise_for_status()
open("labels.pdf", "wb").write(res.content)
```

```js
const res = await fetch("https://api.openzpl.com/v1/printers/8dpmm/labels/4x6/0/", { method: "POST", body: zpl });
if (!res.ok) throw new Error(await res.text());
await require("fs/promises").writeFile("label.png", Buffer.from(await res.arrayBuffer()));
```

Guides per language: https://openzpl.com/guides/zpl-to-pdf-python/, https://openzpl.com/guides/zpl-to-pdf-nodejs/, https://openzpl.com/guides/zpl-to-pdf-php/, https://openzpl.com/guides/zpl-to-pdf-csharp/. Moving from Labelary: https://openzpl.com/migrate/labelary/.
