Labelixa REST API reference: render and barcode endpoints, request/response headers, error codes and API-key authentication.
The free tier works without a key (IP-based limit); no plan adds a watermark to the output. Send the key in the X-API-Key header; it raises the limits. The ?key= query parameter still works but is deprecated (sunset 2027-01-31): query strings leak into browser history, Referer headers, proxy logs and shared links. Requests using it receive Deprecation and Sunset response headers.
Inside /v1 we only add: new response fields, new optional parameters and new endpoints can appear at any time, so ignore fields you do not recognise. A breaking change — a field removed or renamed, a type or meaning changed, an error code redefined — ships as a new major version running alongside this one. When an endpoint or an auth method is withdrawn you get at least six months' notice, Deprecation and Sunset headers carrying the removal date, and afterwards 410 Gone plus a pointer to the replacement — never a silent 404. This is a contractual commitment, not a habit: Terms of Service §24.4.1.
| Method | Path | Description |
|---|---|---|
| POST | /v1/printers/{dpmm}dpmm/labels/{w}x{h}/{index} | Renders the ZPL to a single label (PNG or PDF). |
| POST | /v1/printers/{dpmm}dpmm/labels/{w}x{h}/ | Returns all labels in a single PDF (no index). |
| GET | /v1/barcodes | Produces a single barcode (PNG/SVG/PDF). |
| GET | /v1/barcodes/catalog | Returns the barcode type catalogue (JSON). |
| POST | /v1/graphics | Converts an image to a ZPL graphic command (^GF). State the PHYSICAL size and we do the dot arithmetic: X-Label-MM: 100x150 with X-Dpmm: 8 (203 dpi) prints at exactly that size. Without it one source pixel becomes one printer dot, so the print size is whatever the pixel count happens to be. X-Wrap: label returns a ready-to-print job (^XA/^PW/^LL/^XZ). Every response carries X-Print-Size-MM. Pre-processing: X-Resize-Width/Height, X-Crop, X-Img-Rotate, X-Contrast, X-Dither, X-Invert, X-Threshold. You may also post a PDF: the page is rasterised at your printer resolution (X-Dpmm) and then follows the same path as an image. In a multi-page PDF EACH PAGE BECOMES ONE LABEL (see X-Label-Count), each wrapped in its own ^XA/^XZ; for a single page use X-PDF-Page: 3. Limits: 5 MB, 20 pages, 12 MP per page. |
| POST | /v1/fonts | Converts a TrueType font to a ZPL font command (~DU); subsetting with ?chars=. |
| POST | /v1/fonts/library | Uploads a font into your account's font memory (virtual printer memory); it is auto-prepended to renders that use ^A@. GET lists, DELETE .../{name} removes. Requires an account. |
| POST | /v1/graphics/preview.png | Converts a ^GF command back to PNG (a preview of what reaches the printer). |
| POST | /v1/diagnostics | Analyses ZPL without rendering it and returns a list of findings (JSON). Label context can be given with ?dpmm=&w=&h=. Pass a printer model such as ?model=GK420d and compatibility findings for that model's resolution and width limits are added too (ZPL7xxx). The response's fields array gives the line/column of every ^FD/^FV field — used to jump from the preview back to the code. |
| POST | /v1/fields/map | Returns the editable fields (^FD/^FV) of a ZPL with their start/end offsets in the source text; each field is marked as text or barcode. This is the data source for form-based editors: substitute into the range to change one field without touching the rest of the ZPL. |
| POST | /v1/variables/schema | Extracts the {{variables}} in a template (type/required/default). |
| POST | /v1/variables/bind | Binds JSON data to a template safely; 422 on missing or invalid data. |
| POST | /v1/variables/csv | Uploads a CSV or XLSX; the format is detected from the file CONTENT, not its name. Header/encoding/delimiter detection plus a first-row preview. Legacy .xls and password-protected workbooks are rejected with 400 — save as .xlsx or CSV first. |
| POST | /v1/bulk/jobs | Starts a bulk generation job (1 operation per row; Pro, Business and Enterprise plans). GET /v1/bulk/jobs lists your own jobs; GET .../{id} status, .../{id}/download ZIP, .../{id}/download?bicim=pdf a single merged multi-page PDF, .../{id}/cancel cancels. Without the parameter the response is unchanged (ZIP). A job too large for one PDF returns 413 and states roughly how many labels fit. |
| POST | /v1/designs/zpl | Converts a design JSON document into ZPL. Stateless and NOT stored: the design is never written to disk or logged, and it does not consume quota (a designer must be able to ask for a preview on every edit). Validation runs again on the server; an invalid design returns ALL errors in one response (400). |
| POST | /v1/webhooks | Registers an outgoing webhook endpoint (Pro, Business and Enterprise plans). The signing secret is returned in THIS response only. GET lists, DELETE .../{id} removes. |
| GET | /v1/plans | Lists the plan tiers and their limits. |
| POST | /v1/accounts | Creates an account and generates an API key. |
| GET | /v1/usage | The API key's usage over the last 7 days. Requires a key: calling it without one returns 401. If the key you send is not recognised, the request is still served anonymously and the response carries an X-API-Key-Warning header. |
| GET | /v1/keys | Lists your additional API keys (Business: 3 keys; all draw from the same quota). POST creates one — the full value is returned ONLY in that response; DELETE .../{id} revokes. Management works with the primary key only. |
| Header | Purpose | Values |
|---|---|---|
X-API-Key | API key (also key= in the query — deprecated) | lbx_... |
Idempotency-Key | A retry does not create a second job; the stored response is replayed (/v1/bulk/jobs only) | any unique client-chosen value |
Accept | Output format | image/png (default), application/pdf, application/json, application/zpl, application/epl |
X-Target-Dpmm | Target resolution in ZPL conversion | 6, 8, 12, 24 |
X-Formatter | ZPL formatting (application/zpl) | On, Off |
X-Page-Size | PDF page size | A4, A5, A6, Letter, Legal |
X-Page-Layout | Grid per page | e.g. 2x3 |
X-Quality | PNG quality | Grayscale, Bitonal |
X-Rotation | Rotates the label | 0, 90, 180, 270 |
| Header | Meaning |
|---|---|
X-Total-Count | Total number of labels produced. |
X-Warnings | Skipped/unsupported commands. For EPL output it also leads with what the job writes into the printer itself: the label width (persists after the job) and the form length (drifts if it does not match your media). Read it before printing. |
X-Plan | The plan the request was recognised under. |
X-RateLimit-Remaining | Request quota remaining today. |
| Code | Meaning |
|---|---|
| 400 | Invalid parameter (dpmm, size, etc.). |
| 413 | Request body exceeded 1 MB, the label count exceeded the plan limit, or a single label exceeded 20,000 commands. |
| 429 | Rate/quota limit exceeded; a Retry-After header is returned. |
For the Barcode API, invalid data is returned as an image together with HTTP 200 (for no-code tool compatibility) and the detail is given in the X-Warnings header.
If you send Accept: application/json to the render endpoint, the visible text and barcode fields on the label are returned as JSON together with their coordinates (barcodes are marked with tur: barkod):
curl -X POST "https://api.labelixa.com/v1/printers/8dpmm/labels/4x6/0" \
--data "^XA^FO50,60^FDHello^FS^XZ" -H "Accept: application/json"With Accept: application/zpl your ZPL is reformatted (each command on its own line; disabled with X-Formatter: Off) and, if X-Target-Dpmm is given, the coordinate/size parameters are scaled from the source resolution to the target. Data fields (^FD) do not change.
curl -X POST "https://api.labelixa.com/v1/printers/8dpmm/labels/4x6/0" \
--data-binary @label.zpl -H "Accept: application/zpl" \
-H "X-Target-Dpmm: 12" > label-12dpmm.zplWith Accept: application/epl your label is converted to EPL2, the language used by Zebra's older printers. Text, barcode, box and line fields become EPL2 commands; sizes are computed from the printer's dot density (8dpmm = 203 dpi, 12dpmm = 300 dpi — EPL2 font metrics DIFFER between the two and the right table is selected automatically). Supported barcodes: ^BC Code 128, ^B3 Code 39, ^BE EAN-13, ^B8 EAN-8, ^BU UPC-A, ^B2 Interleaved 2/5, ^BK Codabar.
EPL2 has a narrower command set than ZPL, so not every label converts one-to-one. Nothing is dropped silently: whatever could not be translated is named in the X-Warnings header. Not converted today: QR (^BQ), DataMatrix (^BX), embedded graphics (^GF) and text blocks (^FB). Note also that EPL2 text sizing is not continuous — fixed font x integer multiplier — and the narrow bar width is limited per symbology (1-10 for Code 128, 2-4 for EAN/UPC); a value outside that range is clamped and reported too.
curl -X POST "https://api.labelixa.com/v1/printers/8dpmm/labels/4x6/" \
--data-binary @label.zpl -H "Accept: application/epl" \
-D headers.txt > label.eplInstead of polling for the status of a bulk job we can notify you. Register an endpoint with POST /v1/webhooks; the events are toplu.tamamlandi and toplu.basarisiz.
curl -X POST "https://api.labelixa.com/v1/webhooks" \
-H "X-API-Key: $LABELIXA_KEY" -H "Content-Type: application/json" \
-d '{"url": "https://erp.example.com/labelixa"}'The signing secret is returned in THIS response only and is never shown again in listings; if you lose it, delete the endpoint and create it again. The secret is separate from your account API key: rotating the key does not break webhook verification.
Every request carries Labelixa-Signature: t=<unix>,v1=<hmac>. The HMAC-SHA256 is computed over "<t>.<body>" — the timestamp is INSIDE the signature, so you can reject replays by checking how fresh it is. The format matches Stripe's, so code that already verifies Stripe webhooks can be adapted.
import hmac, hashlib, time
def verify(secret, body, header, tolerance=300):
p = dict(x.split("=", 1) for x in header.split(","))
t = int(p["t"])
if abs(time.time() - t) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + body,
hashlib.sha256).hexdigest()
return hmac.compare_digest(p["v1"], expected)Every request also carries Labelixa-Delivery-Id, and that id does NOT change across retries. If you see the same id twice, do not repeat the work — delivery is at-least-once.
POST /v1/webhooks/{id}/rotate generates a new secret. The old one stays valid for a transition window (24 hours by default), and during that window every request carries TWO signatures (v1=… ,v1=…). Whichever one your verifier knows will match, so you can rotate without downtime. The remaining time is in the rotasyon_bitis field of the GET /v1/webhooks response.
Your verification code must support MULTIPLE v1 values — if you parse the header into a dictionary and read a single value you only see the last one, and you will reject valid signatures during the first half of a rotation.
Any response other than 2xx is a failure and is retried; redirects (3xx) are NOT followed. An endpoint that keeps failing is deactivated automatically — the listing then shows aktif: false and the last status code. The body contains NO row data: only the job id and counters, and you fetch the result with the job id.
Render endpoints are stateless and never store anything; saving happens only through these endpoints, on explicit request. A paid plan is required (Starter 50, Professional 500 saved labels; Enterprise unlimited). This limit is independent of the daily operation quota: one is how many labels you print, the other how many designs you keep.
| Method | Path | Description |
|---|---|---|
| POST | /v1/labels | Saves a label (201). |
| GET | /v1/labels | Lists your labels (no bodies; search, status, folder, favorite, sort, pagination). |
| GET | /v1/labels/{id} | Full content of one label + ETag. |
| PATCH | /v1/labels/{id} | Partial update; If-Match required. |
| DELETE | /v1/labels/{id} | Moves to trash; ?kalici=true deletes permanently. |
| POST | /v1/labels/{id}/restore | Restores from trash. |
| POST | /v1/labels/{id}/duplicate | Duplicates (the copy is a draft with no history). |
| PUT/DELETE | /v1/labels/{id}/favorite | Favorite flag. |
| PUT/DELETE | /v1/labels/{id}/archive | Archive / unarchive. |
| POST | /v1/labels/{id}/publish | Publishes; 409 with findings if the ZPL has errors. |
| POST | /v1/labels/{id}/unpublish | Back to draft. |
| GET | /v1/labels/{id}/versions | Version history (no bodies). |
| GET | /v1/labels/{id}/versions/{n} | Full content of one version. |
| POST | /v1/labels/{id}/versions/{n}/restore | Restores a version (forward: history is never deleted). |
| GET | /v1/labels/{id}/events | Audit trail (event type + time; carries no content). |
| POST | /v1/folders | Creates a folder (single level). |
| GET | /v1/folders | Folders + label counts. |
| PATCH/DELETE | /v1/folders/{id} | Renames / deletes (contents move to root, not deleted). |
| PUT | /v1/labels/{id}/folder | Moves a label into a folder (klasor_id: null = root). |
Every read returns an ETag and every write requires the If-Match header: writing without it returns 428, writing with a stale version returns 412 (the body carries the current version). This prevents a second browser tab from silently overwriting the first one's changes. To overwrite deliberately, send If-Match: *.
Each update snapshots the previous state (the last 50 versions per label are kept). Restoring an old version is written as a new version, so restoring is itself reversible. A deleted label stays in the trash for 30 days and can be restored; active labels are kept indefinitely. Idempotency-Key also applies to POST /v1/labels: retrying with the same key after a network timeout does not create a second label.
Claude, ChatGPT and other MCP-capable AI clients can use Labelixa directly as a tool: the remote MCP server lives at https://api.labelixa.com/mcp (streamable HTTP, JSON-RPC; nothing to install — paste one URL into your client). No key is required — anonymous use is subject to free-tier limits; connect with Authorization: Bearer lbx_... to spend your own account quota.
| Tool | Description |
|---|---|
zpl_preview | Renders ZPL to PNG (dpmm/size/index selectable). |
zpl_validate | Lints ZPL with line/column-located diagnostics. |
barcode_png | Produces a single barcode as PNG. |
MCP calls draw from the same quota and rate limits as REST — MCP is not a separate tier, it is the same API with a tool face. The table above shows highlights; the full, always-current tool list comes from the live server (tools/list) and the /mcp page.
POST /v1/verify reads the barcodes in a photo or scan you upload — the answer to "I printed it, but will the handheld scanner read it?". Send the body as multipart/form-data with the image in the file field; PNG, JPEG, BMP and GIF are accepted, up to 5 MB and 6000×6000 pixels. The response carries olculdu (did the decoder run), an okunan list (each with format and veri) and a not field.
Do not confuse this with POST /v1/barcode-check: there the input is ZPL — we render the label and read back what we drew ourselves. Here the input is an image from the real world.
The limits of the result are printed in the response and should be taken seriously: "read" does NOT guarantee that every handheld scanner will read it, and "not read" does NOT prove the label is bad — the photo's angle, focus or lighting may simply be insufficient. This endpoint is NOT an ISO/IEC 15416 print-quality grade (A–F); that requires a verifier device.
curl -X POST "https://api.labelixa.com/v1/verify" \
-F "file=@label-photo.jpg"To do the same from your browser: Barcode Verifier.
Labelixa renders and validates four printer languages. The endpoints below mirror the ZPL ones: render returns a PNG of the label, diagnostics returns diagnostics with line numbers. Coverage is deliberately an MVP subset per language and every response states its own limits — a command we do not draw is reported, never silently skipped.
| Method | Path | Description |
|---|---|---|
| POST | /v1/language-detect | Detect ZPL / EPL / TSPL / CPCL |
| POST | /v1/epl/render | Render EPL/EPL2 to PNG. A stream may hold several labels (each ended by P): X-Label-Count is the total, ?index= picks one. |
| POST | /v1/epl/diagnostics | EPL diagnostics with line numbers |
| POST | /v1/tspl/render | Render TSPL/TSPL2 to PNG. A stream may hold several labels (each ended by PRINT): X-Label-Count is the total, ?index= picks one. |
| POST | /v1/tspl/diagnostics | TSPL diagnostics with line numbers |
| POST | /v1/cpcl/render | Render CPCL to PNG. A stream may hold several labels (each ended by PRINT): X-Label-Count is the total, ?index= picks one. |
| POST | /v1/cpcl/diagnostics | CPCL diagnostics with line numbers |
| POST | /v1/compatibility | ZPL compatibility risk analysis |
POST /v1/language-detect answers a different question: given code you did not write, which language is it? The answer is heuristic and deterministic (no LLM), and confidence is reported as High/Medium/Low rather than a made-up percentage.
POST /v1/compatibility takes ZPL plus a printer model and returns a compatibility risk analysis — language posture, size-rule findings and preview scope. It is NOT an emulator and never returns a compatibility guarantee: we have physically tested only one of forty models, and a documented emulation mode is not native support.
/embed/viewer is a minimal preview component with no site chrome; you embed it in your own application with an <iframe>. The ZPL travels in the URL fragment (#), not the query string — the fragment is never sent to the server, so your label content cannot land in our logs or any proxy's. The format is IDENTICAL to the preview tool's "copy link" output (base64 JSON), so you can paste that link straight in.
<iframe src="https://labelixa.com/embed/viewer#eyJ6IjoiXlhBLi4uIiwiZCI6IjgiLCJ3IjoiNCIsImgiOiI2In0"
width="420" height="620" style="border:0"></iframe>The height is the one you give; the component fits the image into the frame. The page is noindex and absent from the sitemap — it is a component, not a content page.