API reference

Seven methods, one request shape, two headers.

Your templates live in Qpro+. Your system sends a template name and the field values at the moment of print, and gets the label or document back as markup, ZPL, PDF or SVG, or sent straight to a printer.

Sandbox api.beta.quandosol.com/api Auth X-API-KEY + X-API-SECRET Body label_name · amount · apiData Limit 60 req/min per key Updated 29 September 2026

Endpoints

Pick a method by where the output needs to go.

Every method takes the same body and resolves the same template. They differ only in what comes back and who delivers it to the printer.

#MethodRouteReturnsDelivered toSDK
01Fetch Markups/custom-labels/fetch-markupsMarkup JSONYour appNo
02Print Markups/custom-labels/printBrowser print dialogAny printer the browser seesYes
03PrintNode ZPL/custom-labels/print-nodeStatus JSONZPL printer via PrintNodeNo
04PrintNode PDF/custom-labels/print-node-pdfStatus JSONAny printer via PrintNodeNo
05Export SVG/custom-labels/export-svgFile URLsQpro+ storage, 24 hNo
06Render ZPL/custom-labels/render-zplZPL stringsYour appNo
07Render PDF/custom-labels/render-pdfBase64 PDFYour appNo

Get started

Your first request takes three steps.

01

Get credentials

Generate an API key and secret under API Settings in your Qpro+ account. The secret is shown once.

02

Name the template and fields

Design a template in the canvas and map its API fields. The template name and field names are your contract.

03

Call Fetch Markups

Send the name and your values. Check the response, then switch the path to the output you need.

POST /custom-labels/fetch-markupscurl · sandbox
curl -X POST "https://api.beta.quandosol.com/api/custom-labels/fetch-markups" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $QPRO_API_KEY" \
  -H "X-API-SECRET: $QPRO_API_SECRET" \
  -d '{
    "label_name": "YOUR-TEMPLATE",
    "amount": 1,
    "apiData": { "your_field": "your value" }
  }'

Start with Fetch Markups. If the values in its response are right, every other method renders them the same way, and you've separated “is my data right” from “is my printer set up”.

Get started

Every example here runs against the sandbox.

The sandbox base URL below is the one used throughout this reference. Every route sits under /custom-labels/.

EnvironmentBase URLUse it for
Sandboxhttps://api.beta.quandosol.com/apiQpro+ sandbox accounts: evaluation and development

Production behaves identically. Your production base URL arrives with your production credentials, and moving over means changing the base URL and the key pair, nothing else.

Send requests to the API host, not the app host. beta.quandosol.com serves the web app, and a request sent there can come back 200 with an HTML page. If the response Content-Type isn't JSON, check the host.

Get started

Two headers authenticate every request.

HeaderValue
X-API-KEYYour API key
X-API-SECRETYour API secret, also called the API token
Content-Typeapplication/json

Both values come from API Settings in your Qpro+ account. The secret is visible only when you generate it; store it straight away. Regenerating it invalidates the previous secret immediately, so update every system that uses it at the same time.

Keep both values on your server. Print Markups is the one exception, and it's covered in its section.

Get started

Every endpoint takes the same three fields.

You name the template, say how many copies, and send the field values. Qpro+ resolves the template saved in your account under that name, so a change made in the canvas reaches the very next request without a code change.

Request bodyapplication/json
{
  "label_name": "FG-PALLET-4x6",
  "amount": 1,
  "apiData": {
    "lot_number": "L25-8814",
    "pallet_id": "PLT-4471",
    "piece_qty": "48 CS",
    "customer_code": "NORTHFIELD"
  }
}
FieldTypeRequiredDescription
label_namestringYesExact name of the template in your account. Case-sensitive.
amountintegerYesCopies to produce, 1 or more. Each copy is returned or printed separately.
apiDataobjectYesField values keyed by the field names mapped on the template. Keys are case-sensitive; values are strings.

Three endpoints add fields: print-node and print-node-pdf add printer_id; render-zpl adds dpi and max_width_dots.

Get started

Access, limits, and what counts as a print.

Plan

Production API access needs the Advanced plan. Sandbox trials include API access, so you can evaluate before you buy.

Rate limit

60 requests a minute per key by default. Over that you get 429.

Print allowance

Every render counts toward your plan, including Fetch Markups, Export SVG, Render ZPL and Render PDF.

Method 01 · Preview and debug

Fetch Markups resolves the label and returns it as JSON.

POST/custom-labels/fetch-markups

Returns
Markup JSON
Delivered to
Your app
SDK
Not required
Counts as a print
Yes

Qpro+ loads the template you name, fills every field from apiData, and returns the resolved label as markup: the stage size and every element with its final value. Nothing is rendered to a file and nothing prints.

Use it to confirm your field values resolve before you wire up any output, to feed a renderer of your own, or to inspect exactly what a template contains.

Request body

FieldTypeRequiredDescription
label_namestringYesExact name of the template in your account. Case-sensitive.
amountintegerYesCopies to produce, 1 or more. Each copy is returned or printed separately.
apiDataobjectYesField values keyed by the field names mapped on the template. Keys are case-sensitive; values are strings.

Example request

POST /custom-labels/fetch-markupscurl · bash
curl -X POST "https://api.beta.quandosol.com/api/custom-labels/fetch-markups" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $QPRO_API_KEY" \
  -H "X-API-SECRET: $QPRO_API_SECRET" \
  -d '{
    "label_name": "FG-PALLET-4x6",
    "amount": 1,
    "apiData": {
      "lot_number": "L25-8814",
      "pallet_id": "PLT-4471",
      "piece_qty": "48 CS",
      "customer_code": "NORTHFIELD"
    }
  }'
POST /custom-labels/fetch-markupsJavaScript · node 18+
const res = await fetch("https://api.beta.quandosol.com/api/custom-labels/fetch-markups", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-KEY": process.env.QPRO_API_KEY,
    "X-API-SECRET": process.env.QPRO_API_SECRET
  },
  body: JSON.stringify({
    label_name: "FG-PALLET-4x6",
    amount: 1,
    apiData: {
      lot_number: "L25-8814",
      pallet_id: "PLT-4471",
      piece_qty: "48 CS",
      customer_code: "NORTHFIELD"
    }
  })
});

if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const markups = await res.json();   // one entry per copy
POST /custom-labels/fetch-markupsPython · requests
import os, requests

res = requests.post(
    "https://api.beta.quandosol.com/api/custom-labels/fetch-markups",
    headers={
        "X-API-KEY": os.environ["QPRO_API_KEY"],
        "X-API-SECRET": os.environ["QPRO_API_SECRET"],
    },
    json={
        "label_name": "FG-PALLET-4x6",
        "amount": 1,
        "apiData": {
            "lot_number": "L25-8814",
            "pallet_id": "PLT-4471",
            "piece_qty": "48 CS",
            "customer_code": "NORTHFIELD"
        }
    },
    timeout=15,
)
res.raise_for_status()
markups = res.json()   # one entry per copy

Response

200 OKapplication/json
[
  {
    "stage": {
      "width": 4,
      "height": 6,
      "unit": "in",
      "background": "#ffffff"
    },
    "elements": [
      {
        "type": "text",
        "text": "L25-8814",
        "x": 20,
        "y": 15
      },
      {
        "type": "barcode",
        "format": "code128",
        "text": "PLT-4471"
      },
      {
        "type": "image",
        "url": "https://\u2026",
        "x": 10,
        "y": 80
      }
    ]
  }
]

Trimmed for length. Each element carries its full geometry and styling.

FieldTypeDescription
[ ]arrayOne markup object per copy requested in amount.
stageobjectLabel size and background: width, height, unit, background.
elementsarrayEvery element on the template with its resolved value: text, barcode, image, shapes, lines.

Method 02 · Browser printing

Print Markups renders the label in the browser and opens the print dialog.

POST/custom-labels/print

Returns
Browser print dialog
Delivered to
Any printer the browser sees
SDK
Required
Counts as a print
Yes

The Qpro+ SDK calls this route, receives the same markup as Fetch Markups, draws the label to a canvas inside a hidden iframe, and triggers the browser's native print dialog. Choosing “Save as PDF” in that dialog produces a PDF file.

This is the one method designed to run in a browser. It needs no PrintNode account and no print server.

Prerequisites. The SDK script on the printing page, print-label.html at the path set in printPageUrl, and bwip-js loaded inside print-label.html for barcodes.

Request body

FieldTypeRequiredDescription
label_namestringYesExact name of the template in your account. Case-sensitive.
amountintegerYesCopies to produce, 1 or more. Each copy is returned or printed separately.
apiDataobjectYesField values keyed by the field names mapped on the template. Keys are case-sensitive; values are strings.

Example request

POST /custom-labels/printHTML · setup
<!-- 1. On the page that prints -->
<script src="https://beta.quandosol.com/qpro-label-sdk.js"></script>

<!-- 2. print-label.html, served from your site.
     The SDK loads it into a hidden iframe to render the label. -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Print Label</title>
  <style>
    html, body { margin: 0; padding: 0; background: white; }
    #printRoot { width: 100%; height: 100%; background: white; }
    img { display: block; }
  </style>
</head>
<body>
  <div id="printRoot"></div>
  <script src="https://unpkg.com/bwip-js/dist/bwip-js-min.js"></script>
  <script src="https://beta.quandosol.com/qpro-label-sdk.js"></script>
</body>
</html>
POST /custom-labels/printJavaScript · browser SDK
QPROLabelSDK.setConfig({
  apiBaseUrl:   "https://api.beta.quandosol.com/api",
  api_key:      "YOUR_API_KEY",
  api_token:    "YOUR_API_SECRET",
  printPageUrl: "./print-label.html",
  debug:        false            // true while developing
});

QPROLabelSDK.printLabel({
  label_name: "FG-PALLET-4x6",
  amount: 1,
  apiData: {
    lot_number: "L25-8814",
    pallet_id: "PLT-4471",
    piece_qty: "48 CS",
    customer_code: "NORTHFIELD"
  },
  mode: "print_markups"
});
POST /custom-labels/printcurl · raw route
curl -X POST "https://api.beta.quandosol.com/api/custom-labels/print" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $QPRO_API_KEY" \
  -H "X-API-SECRET: $QPRO_API_SECRET" \
  -d '{
    "label_name": "FG-PALLET-4x6",
    "amount": 1,
    "apiData": {
      "lot_number": "L25-8814",
      "pallet_id": "PLT-4471",
      "piece_qty": "48 CS",
      "customer_code": "NORTHFIELD"
    }
  }'

Supported elements

ElementSupported
Text, multi-line with wrappingYes
ImagesYes
Barcodes: Code 128, Code 39, EAN and moreYes
QR CodeYes
Shapes: rect, circle, ellipse, arc, ring, wedgeYes
LinesYes

Response

Called directly, the route returns the same markup array as Fetch Markups. The SDK does the rendering.

Watch out

Because this method runs in the browser, the key and secret passed to setConfig are readable by anyone who can open the page. Use it on internal, signed-in pages, and use a server-side method anywhere the page is public.

Method 05 · Stored vector files

Export SVG renders the label to SVG files and returns their URLs.

POST/custom-labels/export-svg

Returns
File URLs
Delivered to
Qpro+ storage, 24 h
SDK
Not required
Counts as a print
Yes

Qpro+ resolves the template, renders one SVG per copy on the server, stores the files, and returns a URL for each. The files expire after the period given in ttl_hours, 24 hours today, so download what you need to keep.

Request body

FieldTypeRequiredDescription
label_namestringYesExact name of the template in your account. Case-sensitive.
amountintegerYesCopies to produce, 1 or more. Each copy is returned or printed separately.
apiDataobjectYesField values keyed by the field names mapped on the template. Keys are case-sensitive; values are strings.

Example request

POST /custom-labels/export-svgcurl · bash
curl -X POST "https://api.beta.quandosol.com/api/custom-labels/export-svg" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $QPRO_API_KEY" \
  -H "X-API-SECRET: $QPRO_API_SECRET" \
  -d '{
    "label_name": "FG-PALLET-4x6",
    "amount": 1,
    "apiData": {
      "lot_number": "L25-8814",
      "pallet_id": "PLT-4471",
      "piece_qty": "48 CS",
      "customer_code": "NORTHFIELD"
    }
  }' \
  | jq -r '.files[].url'
POST /custom-labels/export-svgJavaScript · node 18+
const res = await fetch("https://api.beta.quandosol.com/api/custom-labels/export-svg", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-KEY": process.env.QPRO_API_KEY,
    "X-API-SECRET": process.env.QPRO_API_SECRET
  },
  body: JSON.stringify({
    label_name: "FG-PALLET-4x6",
    amount: 1,
    apiData: {
      lot_number: "L25-8814",
      pallet_id: "PLT-4471",
      piece_qty: "48 CS",
      customer_code: "NORTHFIELD"
    }
  })
});

if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const { files } = await res.json();

// Each URL stays live until files[i].expires_at
const svg = await (await fetch(files[0].url)).text();
POST /custom-labels/export-svgPython · requests
import os, requests

res = requests.post(
    "https://api.beta.quandosol.com/api/custom-labels/export-svg",
    headers={
        "X-API-KEY": os.environ["QPRO_API_KEY"],
        "X-API-SECRET": os.environ["QPRO_API_SECRET"],
    },
    json={
        "label_name": "FG-PALLET-4x6",
        "amount": 1,
        "apiData": {
            "lot_number": "L25-8814",
            "pallet_id": "PLT-4471",
            "piece_qty": "48 CS",
            "customer_code": "NORTHFIELD"
        }
    },
    timeout=15,
)
res.raise_for_status()
files = res.json()["files"]
svg = requests.get(files[0]["url"], timeout=15).text

Response

200 OKapplication/json
{
  "status": "svg_exported",
  "ttl_hours": 24,
  "expires_at": "2026-10-01T12:00:00+00:00",
  "files": [
    {
      "url": "https://api.beta.quandosol.com/storage/custom-label-svgs/2026/09/30/fg-pallet-4x6-uuid.svg",
      "path": "custom-label-svgs/2026/09/30/fg-pallet-4x6-uuid.svg",
      "expires_at": "2026-10-01T12:00:00+00:00"
    }
  ]
}
FieldTypeDescription
statusstringsvg_exported on success.
ttl_hoursintegerHours each file stays available.
expires_atstringISO 8601 time the batch expires.
files[].urlstringPublic URL of one SVG, one per copy.
files[].pathstringStorage path of the same file.
files[].expires_atstringISO 8601 time that file expires.

Watch out

Anyone holding a file URL can open it until it expires. If a label carries personal data, keep the URLs server-side and hand your users your own copy.

Method 06 · Your own print path

Render ZPL returns the ZPL to your application.

POST/custom-labels/render-zpl

Returns
ZPL strings
Delivered to
Your app
SDK
Not required
Counts as a print
Yes

The same ZPL as PrintNode ZPL, handed back to you instead of sent to a printer. Use it when you already relay print jobs to your own printers: a raw socket on port 9100, a CUPS raw queue, or an existing print relay. No PrintNode account is involved.

There is no printer to ask for its size here, so tell Qpro+ with dpi and max_width_dots. Wrong values don't raise an error; they print a label that is too wide or too small.

Request body

FieldTypeRequiredDescription
label_namestringYesExact name of the template in your account. Case-sensitive.
amountintegerYesCopies to produce, 1 or more. Each copy is returned or printed separately.
apiDataobjectYesField values keyed by the field names mapped on the template. Keys are case-sensitive; values are strings.
dpiintegerNoPrinter resolution, 100–1200. Default 203.
max_width_dotsintegerNoPrinthead width in dots, 100–10000. Default 832 (4.10 in at 203 dpi).

Example request

POST /custom-labels/render-zplcurl · bash
curl -X POST "https://api.beta.quandosol.com/api/custom-labels/render-zpl" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $QPRO_API_KEY" \
  -H "X-API-SECRET: $QPRO_API_SECRET" \
  -d '{
    "label_name": "FG-PALLET-4x6",
    "amount": 1,
    "dpi": 300,
    "max_width_dots": 1248,
    "apiData": {
      "lot_number": "L25-8814",
      "pallet_id": "PLT-4471",
      "piece_qty": "48 CS",
      "customer_code": "NORTHFIELD"
    }
  }' \
  | jq -r '.labels[0]' | nc 192.168.1.50 9100
POST /custom-labels/render-zplJavaScript · node 18+
const res = await fetch("https://api.beta.quandosol.com/api/custom-labels/render-zpl", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-KEY": process.env.QPRO_API_KEY,
    "X-API-SECRET": process.env.QPRO_API_SECRET
  },
  body: JSON.stringify({
    label_name: "FG-PALLET-4x6",
    amount: 1,
    dpi: 300,
    max_width_dots: 1248,
    apiData: {
      lot_number: "L25-8814",
      pallet_id: "PLT-4471",
      piece_qty: "48 CS",
      customer_code: "NORTHFIELD"
    }
  })
});

if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const { labels } = await res.json();

// Hand each copy to your own printer endpoint
for (const zpl of labels) await sendToPrinter(zpl);
POST /custom-labels/render-zplPython · requests
import os, requests

res = requests.post(
    "https://api.beta.quandosol.com/api/custom-labels/render-zpl",
    headers={
        "X-API-KEY": os.environ["QPRO_API_KEY"],
        "X-API-SECRET": os.environ["QPRO_API_SECRET"],
    },
    json={
        "label_name": "FG-PALLET-4x6",
        "amount": 1,
        "dpi": 300,
        "max_width_dots": 1248,
        "apiData": {
            "lot_number": "L25-8814",
            "pallet_id": "PLT-4471",
            "piece_qty": "48 CS",
            "customer_code": "NORTHFIELD"
        }
    },
    timeout=15,
)
res.raise_for_status()
import socket

for zpl in res.json()["labels"]:
    with socket.create_connection(("192.168.1.50", 9100), timeout=10) as s:
        s.sendall(zpl.encode("ascii"))

Response

200 OKapplication/json
{
  "dpi": 300,
  "max_width_dots": 1248,
  "count": 1,
  "labels": [
    "^XA\n^PW1248\n^LL1800\n^LH0,0\n^FO0,0\n^GFA,\u2026^FS\n^XZ\n"
  ]
}
FieldTypeDescription
dpiintegerResolution the label was rendered at.
max_width_dotsintegerPrinthead width used.
countintegerNumber of labels returned, equal to amount.
labelsstring[]One complete ZPL job per copy, ^XA to ^XZ.

What the ZPL contains

The label is rasterised at the requested density and carried as a single ^GFA graphic field, with ^PW and ^LL set to match it exactly. Fonts, symbologies and artwork are resolved before the ZPL leaves Qpro+, so the printer has nothing to look up. The trade-off: the commands are not editable text or barcode fields.

CommandMeaning
^PWPrint width in dots
^LLLabel length in dots
^FOField origin, x and y
^GFAGraphic field carrying the rendered label

Method 07 · Archive, email, queue

Render PDF returns the PDF to your application.

POST/custom-labels/render-pdf

Returns
Base64 PDF
Delivered to
Your app
SDK
Not required
Counts as a print
Yes

The same vector PDF as PrintNode PDF, returned base64-encoded in the JSON response instead of sent to a printer. The file is built entirely inside Qpro+, with no headless browser and no outside service in the path, so you can print it, archive it, attach it to a shipment, or email it.

Request body

FieldTypeRequiredDescription
label_namestringYesExact name of the template in your account. Case-sensitive.
amountintegerYesCopies to produce, 1 or more. Each copy is returned or printed separately.
apiDataobjectYesField values keyed by the field names mapped on the template. Keys are case-sensitive; values are strings.

Example request

POST /custom-labels/render-pdfcurl · bash
curl -X POST "https://api.beta.quandosol.com/api/custom-labels/render-pdf" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $QPRO_API_KEY" \
  -H "X-API-SECRET: $QPRO_API_SECRET" \
  -d '{
    "label_name": "FG-PALLET-4x6",
    "amount": 1,
    "apiData": {
      "lot_number": "L25-8814",
      "pallet_id": "PLT-4471",
      "piece_qty": "48 CS",
      "customer_code": "NORTHFIELD"
    }
  }' \
  | jq -r '.labels[0]' | base64 --decode > label.pdf
POST /custom-labels/render-pdfJavaScript · node 18+
import fs from "node:fs";

const res = await fetch("https://api.beta.quandosol.com/api/custom-labels/render-pdf", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-KEY": process.env.QPRO_API_KEY,
    "X-API-SECRET": process.env.QPRO_API_SECRET
  },
  body: JSON.stringify({
    label_name: "FG-PALLET-4x6",
    amount: 1,
    apiData: {
      lot_number: "L25-8814",
      pallet_id: "PLT-4471",
      piece_qty: "48 CS",
      customer_code: "NORTHFIELD"
    }
  })
});

if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const { labels } = await res.json();

labels.forEach((b64, i) =>
  fs.writeFileSync(`label-${i + 1}.pdf`, Buffer.from(b64, "base64"))
);
POST /custom-labels/render-pdfPython · requests
import os, requests
import base64

res = requests.post(
    "https://api.beta.quandosol.com/api/custom-labels/render-pdf",
    headers={
        "X-API-KEY": os.environ["QPRO_API_KEY"],
        "X-API-SECRET": os.environ["QPRO_API_SECRET"],
    },
    json={
        "label_name": "FG-PALLET-4x6",
        "amount": 1,
        "apiData": {
            "lot_number": "L25-8814",
            "pallet_id": "PLT-4471",
            "piece_qty": "48 CS",
            "customer_code": "NORTHFIELD"
        }
    },
    timeout=15,
)
res.raise_for_status()
for i, b64 in enumerate(res.json()["labels"], 1):
    with open(f"label-{i}.pdf", "wb") as f:
        f.write(base64.b64decode(b64))

Response

200 OKapplication/json
{
  "mime": "application/pdf",
  "encoding": "base64",
  "count": 1,
  "labels": [
    "JVBERi0xLjcKJeLjz9M\u2026"
  ]
}
FieldTypeDescription
mimestringAlways application/pdf.
encodingstringAlways base64.
countintegerNumber of PDFs returned, equal to amount.
labelsstring[]One base64-encoded PDF per copy.

Watch out

RFID templates are refused with 422 and the message “RFID labels must be rendered as ZPL. PDF cannot encode RFID tags.” Use Render ZPL for those.

Reference

Choose by where the output needs to go.

Your situationMethod
Check that your field values resolve before printing01 · Fetch Markups
Build your own renderer or preview01 · Fetch Markups
Print from a web application through the browser dialog02 · Print Markups
Print to a Zebra or thermal printer from a server03 · PrintNode ZPL
Background or automated warehouse printing03 · PrintNode ZPL
Print to an office or laser printer04 · PrintNode PDF
Labels and documents with complex graphics04 · PrintNode PDF
Get SVG files generated and hosted for you05 · Export SVG
Send ZPL to your own printers, with no PrintNode account06 · Render ZPL
Keep the PDF itself: archive, email, attach, or queue07 · Render PDF

Reference

PrintNode setup is needed for methods 03 and 04 only.

Render ZPL and Render PDF return the label to you, and the other methods don't print server-side, so none of them need PrintNode.

  1. Install the PrintNode client from printnode.com/download on the computer the printer is attached to.
  2. Start the client and connect the printer.
  3. In your PrintNode dashboard, open Printers and copy the printer's ID.
  4. Send that ID as printer_id in the request.

Reference

Five status codes cover what goes wrong.

StatusMeaningCommon causeWhat to do
401UnauthorizedKey or secret missing, wrong, or regenerated since you stored it.Check both headers. Regenerating a token invalidates the old one.
404Not FoundNo template with that label_name in your account.Names are case-sensitive. Copy the name from the template gallery.
422Unprocessable EntityA parameter is missing or invalid. Render ZPL and Render PDF also return 422, not 404, for an unknown label_name. Both PDF methods return 422 for RFID templates.Read the message in the response body; it names the problem.
429Too Many RequestsOver the per-minute limit for your key: 60 requests a minute by default.Back off and retry. Ask us before load-testing toward production volume.
500Server ErrorThe render failed on our side.Retry once, then contact support with the label name and time of the request.

Reference

Questions developers ask in the first hour.

Why does a field print its placeholder instead of my value?
The keys in apiData must match the field names on the template exactly, including capitalisation. A key that doesn't match is ignored: the field prints its placeholder instead of your value, and the response is still 200. Call Fetch Markups and check each element's value.
Why do I get a 404 from one endpoint and a 422 from another for the same label name?
Render ZPL and Render PDF report an unknown label_name as 422; the other methods report it as 404. Treat both as “template not found” in your error handling.
Why is my RFID label refused?
A PDF cannot carry an RFID tag payload, so PrintNode PDF and Render PDF return 422 for any template with an RFID element. PrintNode ZPL and Render ZPL both encode RFID.
Why does my ZPL print too wide or too small?
With Render ZPL there is no printer for Qpro+ to ask, so it uses dpi and max_width_dots from your request, defaulting to 203 dpi and 832 dots. Send the values for the printer you will print on.
Can I edit the text or barcodes inside the ZPL Qpro+ returns?
No. The label is rasterised into a single ^GFA graphic field with fonts, symbologies and artwork already resolved, so the printer has nothing to look up. To change the label, edit the template in the Qpro+ canvas; the next request uses the change.
Nothing reaches my printer through PrintNode. What should I check?
Confirm the PrintNode client is running on the computer the printer is attached to, that the printer shows online in your PrintNode dashboard, and that printer_id matches it.
Do test calls use up my print allowance?
Yes. Fetch Markups, Export SVG, Render ZPL and Render PDF each record a print, as the printing methods do. Develop against your sandbox account and render what you intend to produce.
Can I call the API from browser JavaScript?
Only Print Markups is designed for that, and it exposes your credentials to anyone who can open the page. Call every other method from your backend and pass the result to the browser.
Where do I get an API key and secret?
In your Qpro+ account under API Settings. The secret is shown once, when you generate it, so store it immediately. Regenerating it invalidates the previous one at once.

Reference

Six habits that keep integrations quiet.

Keep credentials server-side

Proxy calls through your backend. Never ship the secret in browser code, a mobile app, or firmware a customer can read.

Prove the data first

Use Fetch Markups while developing to confirm every field resolves before you wire up an output.

Validate before you send

Check apiData values in your own system, so a bad value is caught before it becomes a printed label.

Match the hardware

Send dpi and max_width_dots on Render ZPL for the printer you will print on.

Download what you keep

Export SVG files expire. Copy them to your own storage if you need them past ttl_hours.

Render what you mean to produce

Every render counts toward your plan. Don't call render endpoints in a test loop.

For machines

The same reference, in formats your tools read.

Generate a client, import into Postman, or point a coding assistant at the spec. All three files are built from the same source as this page, so they never disagree with it.

Building an agent? Ready-made tool definitions for Claude, OpenAI, Gemini and Microsoft 365 Copilot are in /ai/. Each describes the same six routes, with print confirmation and allowance warnings written into the tool descriptions.

Start free

Render a real label in your sandbox today.

A free sandbox account includes API access, so you can call every method above before you buy.

Qpro+ is a product of Quando Solutions, a supply chain & manufacturing IT company