Sign In
Products
ZPL PreviewBarcode GeneratorImage → ZPLZPL cheat sheetLabel DesignerTemplate Gallery
Developers
API QuickstartAPI ReferenceMCP ServerBulk GenerationWebhooksSDKs & ExamplesLimitsOn-Premise
Solutions
Logistics & ShippingE-CommerceERP & WMSManufacturingRetailHealthcare & LaboratoryAll solutions
Tools
ZPL Diagnostics HubZPL DebuggerSize & DPI AnalyzerDPI ConverterBarcode fit calculatorShipping Label AnalyzerFont InspectorOrientation DebuggerCalibration AssistantPrinter Compatibility CheckerBarcode Readability CheckerPrinter Language DetectorThermal Printer Test PackZPL ToolsEPL ToolsTSPL ToolsCPCL ToolsAll ToolsGuidesPricing
Language
EnglishTürkçeDeutsch
HomeDevelopersAPI Reference

API Reference

Labelixa REST API reference: render and barcode endpoints, request/response headers, error codes and API-key authentication.

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.

API stability

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.

Endpoints

MethodPathDescription
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/barcodesProduces a single barcode (PNG/SVG/PDF).
GET/v1/barcodes/catalogReturns the barcode type catalogue (JSON).
POST/v1/graphicsConverts 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/fontsConverts a TrueType font to a ZPL font command (~DU); subsetting with ?chars=.
POST/v1/fonts/libraryUploads 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.pngConverts a ^GF command back to PNG (a preview of what reaches the printer).
POST/v1/diagnosticsAnalyses 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/mapReturns 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/schemaExtracts the {{variables}} in a template (type/required/default).
POST/v1/variables/bindBinds JSON data to a template safely; 422 on missing or invalid data.
POST/v1/variables/csvUploads 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/jobsStarts 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/zplConverts 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/webhooksRegisters 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/plansLists the plan tiers and their limits.
POST/v1/accountsCreates an account and generates an API key.
GET/v1/usageThe 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/keysLists 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.

Request headers

HeaderPurposeValues
X-API-KeyAPI key (also key= in the query — deprecated)lbx_...
Idempotency-KeyA retry does not create a second job; the stored response is replayed (/v1/bulk/jobs only)any unique client-chosen value
AcceptOutput formatimage/png (default), application/pdf, application/json, application/zpl, application/epl
X-Target-DpmmTarget resolution in ZPL conversion6, 8, 12, 24
X-FormatterZPL formatting (application/zpl)On, Off
X-Page-SizePDF page sizeA4, A5, A6, Letter, Legal
X-Page-LayoutGrid per pagee.g. 2x3
X-QualityPNG qualityGrayscale, Bitonal
X-RotationRotates the label0, 90, 180, 270

Response headers

HeaderMeaning
X-Total-CountTotal number of labels produced.
X-WarningsSkipped/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-PlanThe plan the request was recognised under.
X-RateLimit-RemainingRequest quota remaining today.

Error codes

CodeMeaning
400Invalid parameter (dpmm, size, etc.).
413Request body exceeded 1 MB, the label count exceeded the plan limit, or a single label exceeded 20,000 commands.
429Rate/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.

Data extraction (JSON)

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
curl -X POST "https://api.labelixa.com/v1/printers/8dpmm/labels/4x6/0" \
     --data "^XA^FO50,60^FDHello^FS^XZ" -H "Accept: application/json"

ZPL conversion (application/zpl)

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 — 8→12 dpmm
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.zpl

EPL2 output (application/epl)

With 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
curl -X POST "https://api.labelixa.com/v1/printers/8dpmm/labels/4x6/" \
     --data-binary @label.zpl -H "Accept: application/epl" \
     -D headers.txt > label.epl

Outgoing webhooks

Instead 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 — register an endpoint
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.

Python — verification
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.

Secret rotation

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.

Saved labels

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.

MethodPathDescription
POST/v1/labelsSaves a label (201).
GET/v1/labelsLists 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}/restoreRestores from trash.
POST/v1/labels/{id}/duplicateDuplicates (the copy is a draft with no history).
PUT/DELETE/v1/labels/{id}/favoriteFavorite flag.
PUT/DELETE/v1/labels/{id}/archiveArchive / unarchive.
POST/v1/labels/{id}/publishPublishes; 409 with findings if the ZPL has errors.
POST/v1/labels/{id}/unpublishBack to draft.
GET/v1/labels/{id}/versionsVersion history (no bodies).
GET/v1/labels/{id}/versions/{n}Full content of one version.
POST/v1/labels/{id}/versions/{n}/restoreRestores a version (forward: history is never deleted).
GET/v1/labels/{id}/eventsAudit trail (event type + time; carries no content).
POST/v1/foldersCreates a folder (single level).
GET/v1/foldersFolders + label counts.
PATCH/DELETE/v1/folders/{id}Renames / deletes (contents move to root, not deleted).
PUT/v1/labels/{id}/folderMoves a label into a folder (klasor_id: null = root).

Concurrency (If-Match)

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: *.

Versions and trash

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.

MCP server for AI assistants

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.

ToolDescription
zpl_previewRenders ZPL to PNG (dpmm/size/index selectable).
zpl_validateLints ZPL with line/column-located diagnostics.
barcode_pngProduces 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.

Reading a printed label back

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
curl -X POST "https://api.labelixa.com/v1/verify" \
     -F "file=@label-photo.jpg"

To do the same from your browser: Barcode Verifier.

Printer languages beyond ZPL

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.

MethodPathDescription
POST/v1/language-detectDetect ZPL / EPL / TSPL / CPCL
POST/v1/epl/renderRender 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/diagnosticsEPL diagnostics with line numbers
POST/v1/tspl/renderRender 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/diagnosticsTSPL diagnostics with line numbers
POST/v1/cpcl/renderRender 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/diagnosticsCPCL diagnostics with line numbers
POST/v1/compatibilityZPL 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.

Embedding the preview in your own page

/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.

HTML
<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.