API reference

Every HTTP endpoint of your install, grouped by product. Each links to the page with the details and code. The machine-readable spec is openapi.yaml (OpenAPI 3.1).

Base URL: https://labelviewer.example.internal (your install). Requests and responses are UTF-8.

Endpoints#

Each product's endpoints need a license that includes it, else they answer 402 (Errors, License). The server endpoints below always work.

Viewer: render ZPL to PNG#

EndpointGuideRequest → response
POST /v1/printers/{dpmm}dpmm/labels/{W}x{H}/{index}/One label as a PNGRaw ZPL (or a form field file) → one PNG; JSON with Accept: application/json. Headers X-Total-Count, X-Warnings. Compatible with Labelary: only the host changes.
POST /v1/renderEvery label as JSON{ zpl, dpmm?, widthIn?, heightIn?, index?, upright? } → { dpmm, total, labels: [{ index, width, height, png }], warnings } (PNG as base64)

Editor: fill a template#

EndpointGuideRequest → response
POST /v1/mergePrint from your backend, Templates & variables{ template, data, missing? } → text/plain ZPL, one copy per record. Header X-Record-Count.

Server#

EndpointGuideResponse
GET /v1/infobelow{ name, version, license, sdk }
GET /healthzbelow{ "status": "ok", "auth": false, "license": "<status>" }
GET /sdk/<version>/<file>, GET /sdk/latest/<file>below, SDKThe browser SDK files

GET /v1/info#

The same answer for every caller; no configuration values and never the license text. The Status page reads it.

{"name": "labelviewer", "version": "1.0.1",
 "license": {"status": "valid", "trial": false, "source": "configured", "allowed": {"viewer": true, "editor": true},
             "products": ["viewer", "editor"], "licensee": "Example Co", "id": "LIC-0007", "expires": "2027-12-31",
             "daysLeft": 437, "maxInstances": 2, "instanceId": "lv-3f2a9c0e41d7b6a85c19e2f0"},
 "sdk": {"version": "1.0.1", "url": "/sdk/1.0.1/", "latest": "/sdk/latest/"}}
license field
statusvalid, expired (past the end of its expiry date, UTC), missing (no license) or invalid (the key was not accepted; the reason is in the server log, never here)
trialtrue for a trial license: label images are watermarked
sourceconfigured (LICENSE_KEY / LICENSE_FILE) or bundled (the evaluation license in the download); absent when missing
allowedWhether each product works on this installation now
productsThe products in the license
licensee, id, expires, daysLeft, maxInstancesFrom the license. expires and daysLeft are absent for a trial without an expiry date
instanceIdThis installation's id (set LABELVIEWER_INSTANCE_ID to choose it)

See License. sdk is null when the server has no SDK build.

GET /healthz#

For container and load-balancer health checks: 200 whatever the license state (license is its status). auth is always false (kept for older clients). See Install › Health checks.

SDK files#

labelviewer-elements.min.js, labelviewer-elements.js, fonts/*, index.d.ts, react.d.ts, vue.d.ts, labelviewer-elements-<version>.tgz and manifest.json (every file with its size and SRI hash). Versioned URLs are Cache-Control: public, max-age=31536000, immutable; latest URLs are no-cache with an ETag. Only the installed version exists. How to load them: SDK.

Legacy aliases#

POST /api/render and POST /api/merge behave like /v1/render and /v1/merge and keep the legacy error shape (below). New integrations should use /v1/*.

No authentication#

The API has no keys and no tokens, by design: labelviewer is an internal service you run next to the systems that print labels. Every endpoint can be called by anything that can reach the server. If your deployment needs access control, put the server behind your own gateway, VPN or reverse proxy and enforce it there (Install › Access control).

CORS#

Every API response (/v1/*, /api/*, /healthz) and every SDK file (/sdk/...) carries Access-Control-Allow-Origin: *, so browser apps on any origin can call the API. Preflight requests (OPTIONS) answer 204 with Access-Control-Allow-Methods: GET, HEAD, POST, OPTIONS, Access-Control-Allow-Headers: Content-Type, Accept, X-Request-Id and Access-Control-Max-Age: 600. Access-Control-Expose-Headers lets pages read X-Total-Count, X-Warnings, X-Record-Count and X-Request-Id. Credentials (cookies) are never used or allowed.

Errors#

/v1/* answers errors as JSON:

{"error": {"code": "merge_error", "message": "1 merge error", "details": [{"record": 0, "placeholder": "name", "reason": "missing value"}], "requestId": "4f0c…"}}

details is added when there is structured data. requestId equals the X-Request-Id response header. Send your own X-Request-Id ([A-Za-z0-9._-]{1,64}) to correlate logs; otherwise the server generates one.

The legacy /api/render and /api/merge answer {"error": "<message>", "code", "details"?, "requestId"}. The Labelary-compatible route answers plain text unless the request asked for JSON (Accept: application/json or a JSON Content-Type), as Labelary does; its 402 license errors are always JSON.

StatuscodeWhen
400invalid_requestMalformed JSON, wrong field types, bad dpmm/size/index
402license_requiredNo license, or the configured license key is not valid. Install a valid key (LICENSE_KEY / LICENSE_FILE) and restart
402license_expiredThe license expired (there is no grace period). Install the renewed key and restart
402product_not_licensedThe license does not include this product (Viewer: render routes; Editor: merge routes)
404not_foundUnknown route, or index past the last label
405 / 406method_not_allowed / not_acceptableWrong method (see Allow) / an Accept that allows neither PNG nor JSON
413payload_too_large / merge_limitBody over the limit / too many records or output too large
422merge_errorTemplate or data problems, listed in details
422unprocessableThe ZPL exceeds a render limit or the time budget, or produced no labels (PNG)
503unavailableThe render queue is full; retry with backoff
500internalBug; logged on the server with the request id

Messages never contain your data.

Limits#

Every request is bounded, so one bad template or upload cannot take the server down:

LimitValueError
Request body (render, merge, Labelary)8 MiB413 payload_too_large
Whole request (headers + body)30 s (REQUEST_TIMEOUT_MS); headers within 10 sconnection closed
Render time10 s per render (RENDER_TIMEOUT_MS); the worker is replaced422 unprocessable
Render memory512 MiB heap per render worker (RENDER_MAX_HEAP_MB)422 unprocessable
Render queue64 waiting renders (RENDER_MAX_QUEUE)503 unavailable (retry with backoff)
Labels0.1 to 15 in per side; at most 50 labels per render (more are cut off with a warning); pixel and work budgets inside the renderer400 / 422
Merge1000 records, 1 MiB template, 16 MiB output, 64 KiB per value, 4096 placeholders413 merge_limit

The environment variables are described in Install › Configuration.