API reference

LabelCraft - barcode labels for Shopify that print at exactly the size you set.

The LabelCraft API prints labels and reads templates from outside the Shopify admin: a warehouse system, a receiving script, a scheduled job. It is the app's own print engine reached with a key instead of a session, so a label printed through the API is the label the app would have printed for the same template and the same products, warnings included.

It is part of the Warehouse plan. Keys are created in LabelCraft, under Settings, API access.

Authentication

Send the key as a bearer token on every request. Keys start with lc_live_ and are shown once, when created. A shop holds at most 5 active keys; revoking one takes effect on the next request.

A request without a valid key answers 401. A valid key on a shop below the Warehouse plan answers 402.

curl https://labelcraft.tech/api/v1/templates \
  -H "Authorization: Bearer lc_live_0123456789abcdef0123456789abcdef01234567"

Limits

GET /api/v1/templates

Lists the built-in paper sizes (by slug) and the shop's own templates (by id), with the label size in millimetres and the resolution the template was designed at. Archived templates are not listed and cannot be printed.

curl https://labelcraft.tech/api/v1/templates \
  -H "Authorization: Bearer YOUR_KEY"
{
  "templates": [
    {
      "id": "cmf1x9q2h0001",
      "name": "Shelf 62 x 29",
      "kind": "roll",
      "source": "custom",
      "labelWidthMm": 62,
      "labelHeightMm": 29,
      "dpi": 300,
      "version": 3
    },
    {
      "id": "dymo-30252",
      "name": "Dymo 30252 Address (3½ × 1⅛ in)",
      "kind": "roll",
      "source": "preset",
      "labelWidthMm": 88.89999999999999,
      "labelHeightMm": 28.575,
      "dpi": 203,
      "version": 1
    }
  ]
}

POST /api/v1/print

Renders one PDF for a template and a list of variants with quantities, records the job in the shop's print history (source API, with the key's name), and returns the bytes as base64 beside the warnings.

Variants are read live from the shop at print time: title, SKU, barcode, price, weight, metafields. You send ids and quantities, nothing else.

curl https://labelcraft.tech/api/v1/print \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template": "dymo-30252",
    "items": [
      { "variantId": 45678901234567, "qty": 2 },
      { "variantId": "gid://shopify/ProductVariant/45678901234568", "qty": 1 }
    ]
  }'
{
  "jobId": "cmf1xa77c0003",
  "filename": "labels-dymo-30252.pdf",
  "pageCount": 3,
  "labelCount": 3,
  "warnings": [
    {
      "code": "textOverflow",
      "labelIndex": 1,
      "element": "productTitle",
      "message": "Text overflows its box: \"Organic cotton crew neck, heavyweight, oversized fit\""
    }
  ],
  "pdfBase64": "JVBERi0xLjcK..."
}

GET /api/v1/templates/:id

One template with its full layout: a built-in paper size by slug or one of the shop's non-archived templates by id. The layout is the exact object the editor stores, so a system can read a paper size, change a few fields and POST the result under its own name.

curl https://labelcraft.tech/api/v1/templates/dymo-30252 \
  -H "Authorization: Bearer YOUR_KEY"

POST /api/v1/templates

Creates a template from { name, layout } and answers 201 with the same shape as GET /templates/:id. The layout goes through the editor's own rules: unknown fields are dropped, fields that only make sense on some element types are removed from the others, a logo or a custom font must decode, and an element outside the label is refused with 422 and the box that would fit.

A template created here appears in the app's template list and prints from the app and from POST /print alike.

curl https://labelcraft.tech/api/v1/templates \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Shelf 62 x 29",
    "layout": {
      "kind": "roll", "labelWidthMm": 62, "labelHeightMm": 29, "dpi": 300,
      "elements": [
        { "type": "productTitle", "xMm": 2, "yMm": 2, "widthMm": 58, "heightMm": 8, "fontSize": 9 },
        { "type": "barcode", "xMm": 2, "yMm": 11, "widthMm": 58, "heightMm": 16, "barcodeFormat": "ean13" }
      ]
    }
  }'

PATCH /api/v1/templates/:id

Changes the name, the layout or both. Every change bumps the version and stores an immutable snapshot, so a job printed before the change reprints exactly as it did. Built-in paper sizes answer 409 template_read_only: GET the paper size, POST it under your name, then edit that one.

curl -X PATCH https://labelcraft.tech/api/v1/templates/cmf1x9q2h0001 \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Shelf 62 x 29 (v2)" }'

DELETE /api/v1/templates/:id

Archives the template: it leaves the list and can no longer print, and answers 204. Nothing is erased, so past jobs keep reprinting from their snapshots. Built-in paper sizes answer 409.

curl -X DELETE https://labelcraft.tech/api/v1/templates/cmf1x9q2h0001 \
  -H "Authorization: Bearer YOUR_KEY"

POST /api/v1/barcodes/audit

Runs the app's barcode check over a list of values, the same check behind the Barcode check page and the pre-print warnings. Nothing is read from the shop and nothing is written: send the codes your system holds and get, for each, whether it is a valid retail GTIN, missing, invalid (with the corrected value when only the check digit is wrong), a restricted prefix (in-store, coupon, ISBN) or an internal Code 128 value.

curl https://labelcraft.tech/api/v1/barcodes/audit \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "values": ["4006381333931", "4006381333932", "SKU-0042"] }'
{
  "results": [
    { "value": "4006381333931", "status": "valid", "format": "GTIN-13", "message": "..." },
    { "value": "4006381333932", "status": "invalid", "suggestion": "4006381333931", "message": "..." },
    { "value": "SKU-0042", "status": "internal", "message": "..." }
  ]
}

Errors

Every error is a JSON object { error: { code, message } } with the extra fields named below. The message says what to change; the code is stable.

StatusCodeWhen
401unauthorizedno bearer token, unknown, malformed or revoked key, or the shop uninstalled the app
402plan_requiredthe key's shop is below the Warehouse plan; carries plan and pricingUrl
429rate_limitedmore than 60 requests in the trailing minute; carries retryAfterSeconds and a Retry-After header
413payload_too_largethe body is above 1048576 bytes; carries maxBytes
400invalid_requestthe body is not valid; details[] names each field
400too_many_labelsthe quantities sum above 2000; carries max and requested
404template_not_foundno paper size slug and no non-archived template of this shop matches
409template_read_onlyPATCH or DELETE on a built-in paper size; create your own from it
409template_conflictanother change landed on the template while yours was saving; GET it again and retry. Nothing saved
409template_limit_reachedPOST /templates past 200 non-archived templates; carries max and count
500write_faileda template write failed after validation; carries a reference to quote to support. Nothing saved
422layout_not_printablean element sits outside the label; carries faults[] with the element, the overflow per edge and the box that would fit. Nothing saved
422variants_not_foundone or more variant ids returned nothing on this shop; carries missing[]. Nothing printed, nothing counted
405method_not_allowedwrong HTTP method; the Allow header names the right one
503shop_unavailablethe shop's offline access is gone, or Shopify refused the token (uninstalled, frozen or locked store); carries detail. Open LabelCraft once in the Shopify admin of that shop, then retry
500render_failedsomething failed after validation; carries a reference to quote to support. The job was not recorded and nothing was counted

What the API shares with the app

The print endpoint calls the same functions the in-app print calls: the same variant read, the same money formatting, the same metafield resolution, the same brand kit rules for the plan, the same font and barcode floors before and after the merchant's own printer calibration, and the same render engine. A test in the code base fails if one of those calls is replaced by a copy or dropped.

What the API does not do: it never asks for a review, never pushes to PrintNode, never appends the calibration ruler, and never offers the in-app metafield fixes. Delivery to a printer is yours.

Coming next

Generating barcodes for products that have none (writing to the catalogue) is planned as a further slice. It is not available yet, and this page only documents what the code does today.

Questions, or a system you want to connect: support@labelcraft.tech.

Available on Shopify App Store