Quick start
curl -X POST "https://blznworks.com/api/zpl/render?width=4&height=6&dpi=203" \
--data-binary @labels.zpl -H "Content-Type: text/plain" -o labels.pdf
That's it: each ^XA … ^XZ block in labels.zpl becomes one page of labels.pdf, at 4 × 6 inches. Without a key you are on the free plan (5 labels per request, 50 per day per IP address).
Endpoint
POST https://blznworks.com/api/zpl/render
Query parameters
| Name | Default | Values |
|---|---|---|
width, height | 4, 6 | Label size, decimal with a dot. 10–381 mm (0.4–15 in) per side. |
unit | in | in or mm |
dpi | 203 | 203, 300 or 600. Or dpmm = 8, 12, 24. Use the density the ZPL was written for. |
format | pdf | pdf: one page per label. png: a PNG for one label, a zip of PNGs for several. json: base64 PNG previews of the first 12 labels, see below. |
Body
- ZPL text (
Content-Type: text/plain), UTF-8 or a single-byte code page, any number of labels. - Or, with Pro, a zip of
.zpl,.txtor.prnfiles (Content-Type: application/zip). Files are read in name order, so zero-pad numbers (001.zpl,002.zpl).
Authentication
Optional. Send your Pro license key as Authorization: Bearer BLZN-XXXXX-… (or X-Api-Key). The same key works in the web tool. The API counts as one device of the license.
Responses
200 with the file (application/pdf, image/png or application/zip) and these headers:
| Header | Meaning |
|---|---|
X-Label-Count | Labels found and rendered |
X-Plan | free or pro |
X-Labels-Remaining, X-Labels-Limit, X-Labels-Reset | Quota left, the daily (free) or monthly (Pro) limit, and when it resets (UTC, ISO 8601) |
X-Zpl-Warnings | Number of notes, e.g. printer-only commands such as ^PQ that were ignored. format=json lists them. |
format=json returns { ok, labels, previewed, warnings, images, plan, remaining, limit, resetsAt }, where images are base64 PNGs of up to 12 labels, as the web tool's preview shows. Only previewed labels count toward the quota. Use it for previews; use pdf or png for output.
Errors
Errors are JSON: { "ok": false, "error": "too_many_labels", "message": "…" }.
| Status | error | What to do |
|---|---|---|
| 400 | invalid_size, invalid_dpi, invalid_format, no_labels, size, empty, zip_invalid | Fix the request; message says what is wrong |
| 401 | invalid_key | Check the key, or leave it out to use the free plan |
| 402 | too_many_labels, zip_requires_pro | The request needs Pro, or more than the per-request limit: split it |
| 403 / 409 | license_inactive / device_limit | The license ended, was refunded, or all its devices are in use |
| 413 | too_large | Body over 512 KB (free) or 16 MB (Pro) |
| 422 | render_failed | A label could not be drawn; message names it |
| 429 | quota_exceeded, or no body | Daily/monthly quota used, or more than 20 requests a minute (120 with a key). Wait, then retry |
| 503 | busy, timeout | Retry after Retry-After seconds; split very large batches |
Examples
curl
# Free: one label, PNG at 300 dpi, 100 x 150 mm
curl -X POST "https://blznworks.com/api/zpl/render?width=100&height=150&unit=mm&dpi=300&format=png" \
--data-binary "^XA^FO50,50^A0N,60,60^FDHello^FS^XZ" -o label.png
# Pro: a zip of many .zpl files to one PDF
curl -X POST "https://blznworks.com/api/zpl/render?width=4&height=6&dpi=203" \
-H "Authorization: Bearer $BLZN_KEY" -H "Content-Type: application/zip" \
--data-binary @labels.zip -o labels.pdf
C# (.NET 6+)
using System.Net.Http.Headers;
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("BLZN_KEY"));
var zpl = await File.ReadAllTextAsync("labels.zpl");
using var response = await http.PostAsync(
"https://blznworks.com/api/zpl/render?width=4&height=6&dpi=203&format=pdf",
new StringContent(zpl, System.Text.Encoding.UTF8, "text/plain"));
if (!response.IsSuccessStatusCode)
throw new Exception(await response.Content.ReadAsStringAsync()); // {"ok":false,"error":"…","message":"…"}
await File.WriteAllBytesAsync("labels.pdf", await response.Content.ReadAsByteArrayAsync());
Console.WriteLine($"Labels: {response.Headers.GetValues("X-Label-Count").First()}, left: {response.Headers.GetValues("X-Labels-Remaining").First()}");
JavaScript (Node 18+ or the browser)
import { readFile, writeFile } from "node:fs/promises";
const zpl = await readFile("labels.zpl", "utf8");
const response = await fetch("https://blznworks.com/api/zpl/render?width=4&height=6&dpi=203&format=pdf", {
method: "POST",
headers: { "Content-Type": "text/plain", Authorization: `Bearer ${process.env.BLZN_KEY}` },
body: zpl,
});
if (!response.ok) throw new Error((await response.json()).message);
await writeFile("labels.pdf", Buffer.from(await response.arrayBuffer()));
console.log("labels:", response.headers.get("X-Label-Count"), "left:", response.headers.get("X-Labels-Remaining"));
The API sends Access-Control-Allow-Origin: * and reads no cookies, so it also works from a browser page. Never put a Pro key in public web pages: call the API from your server.
Plans and limits
| Free (no key) | Pro | |
|---|---|---|
| Labels per request | 5 | 500 |
| Quota | 50 labels a day per IP | 20,000 labels a month per license |
| Body | 512 KB of ZPL | 16 MB, ZPL or zip (up to 64 MB unpacked) |
| Requests per minute | 20 per IP | 120 per IP |
| Price | $0 | $12 / month or $120 / year |
Current limits are also served as JSON at GET https://blznworks.com/api/zpl/limits.
Rendering notes
- Output is rendered at the chosen print density and placed on pages of the exact label size, so a PDF prints 1:1. Choose the dpi the ZPL was designed for: a 203 dpi label rendered at 300 dpi looks smaller.
- Supported: text and fonts (
^A,^CF,^FB,^FH,^CI), boxes, lines, circles and ellipses, graphics (^GF,~DG,^XG,^IM), stored formats (^DF/^XF), reverse print, and barcodes Code 128, Code 39, Code 93, EAN-13, UPC-A/E, Interleaved 2 of 5, Codabar, QR, Data Matrix, PDF417, Aztec and MaxiCode. - Printer-only commands (
^PQcopies, media, speed, darkness) do not change how a label looks and are ignored with a note. - Privacy: the request body is held in memory while it is rendered and then discarded. Nothing is written to disk or logged except label counts for quotas.
Questions or a command that renders wrongly? Contact support with a sample label.