Public API

REST API on api.zplcloud.com for rendering designs, converting and checking ZPL, enriching carrier labels, running batch jobs and reading printers, agents and quota. The rendering, tool, enrichment, batch, printer, agent and account endpoints can call one of your webhooks when they have finished - signed, retried and, if you ask for it, with the result.

Base URL and authentication

ItemDetail
Base URLhttps://api.zplcloud.com - paths start with /v1, there is no /api prefix.
HeaderX-API-Key: sk_zplcloud_…
Basic AuthAPI key as user name, empty password: curl -u sk_zplcloud_…:
Key prefixessk_zplcloud_ for normal keys, sk_sandbox_ for sandbox keys.
Creating keysPlatform → API Keys (/platform/api). Keys are always generated server-side. The full key is shown only once, right after creating it - zplCloud stores only a SHA-256 hash, so a lost key cannot be recovered: create a new one and delete the old one. A key belongs to the user who created it and, if that user is in a company, to the company.
LanguageAlways English: error messages, hints and field names.
Referenceapi.zplcloud.com/scalar - interactive reference with request builder for every public endpoint, grouped by topic (see the endpoint index below).
# X-API-Key header
curl https://api.zplcloud.com/v1/quota -H "X-API-Key: sk_zplcloud_YOUR_KEY"

# Basic Auth: key as user name, empty password (note the colon)
curl https://api.zplcloud.com/v1/quota -u sk_zplcloud_YOUR_KEY:

What a key can reach follows its owner: designs, printers, agents, webhooks and batch jobs of the key's company - or of the owner's e-mail for keys without a company. Anything outside that scope is treated as not found.

Sandbox keys

Sandbox keys (sk_sandbox_…) are free and consume no renders, but they only work for /v1/virtual-printers, whose output is a watermarked PNG. Every other /v1 path answers 403 with a hint. Use a normal key for everything on this page.

Conventions

  • Requests: JSON bodies with camelCase field names and Content-Type: application/json. Unknown fields are ignored.
  • Binary input: PDFs and images are base64 strings inside the JSON body. Data URIs such as data:application/pdf;base64,JVBERi0x… are accepted.
  • Responses: files (PDF, PNG, JPG, SVG, raw ZPL) come back as the response body with the matching Content-Type; everything else is JSON.
  • Request ID: every response of a /v1 endpoint carries X-ZplCloud-Request-Id (GUID) - log it and quote it in support requests. On the endpoints with webhook support the same ID also appears as requestId in error bodies and in webhook callbacks.
  • Timestamps are UTC. Values read from the database are written without a trailing Z (2026-09-11T08:12:44.193) - treat them as UTC.
  • Webhook callback: the query parameters webhookId and webhookResult work on design rendering as PDF and PNG, the ZPL tools, label enrichment, batch jobs, the printer and agent lists, quota and webhooks - not on the endpoints under Other endpoints. See Webhook callback.

Errors

Errors are JSON with a readable error, usually a hint, and - on the endpoints with webhook support - the requestId. Some errors add fields: limit, used and requested on 429, plan on 403 when an endpoint needs the Pro plan, tool on tool errors, jobId on failed batch jobs.

HTTP/1.1 429 Too Many Requests
Content-Type: application/json; charset=utf-8
X-ZplCloud-Request-Id: 8b1d6c3e-2f4a-4e0b-9a7d-5c3e1f2a9b64

{
  "error": "Render limit reached (9950/10000 labels this month, this request needs 500).",
  "hint": "GET /v1/quota shows your current label quota. Upgrade your plan or buy a render top-up under Billing in the platform. The ZPL tools under /v1/tools are not limited.",
  "limit": 10000,
  "used": 9950,
  "requested": 500,
  "requestId": "8b1d6c3e-2f4a-4e0b-9a7d-5c3e1f2a9b64"
}

Requests rejected before any processing - missing or invalid API key, unknown or disabled webhookId, sandbox key - return error and hint only, without requestId in the body. The 403 for a sandbox key is sent before the endpoint runs and has no X-ZplCloud-Request-Id header either.

Status codes

CodeMeaning
200Success. POST /v1/batch/design/{designId} with "async": true answers 202 Accepted.
400Invalid JSON or field values, invalid designId, unknown or disabled webhookId, webhookResult=true without webhookId.
401API key missing or invalid.
403Sandbox key used outside /v1/virtual-printers, or the key owner is not on the Pro plan, which batch jobs and label enrichment require.
404Design, batch job or printer not found in the key owner's scope; label enrichment not enabled.
413Body or input too large: body size, number of records, characters, file size, pages.
429Label quota used up - only design renders (ZPL, PDF, PNG) and batch jobs.
499The client closed the connection before the request finished (shows up in logs and statistics, not in your client).
503Temporarily unavailable (database), also when the plan cannot be read for a Pro endpoint. Retry with backoff.

Label quota and render counting

Labels are counted per account - the e-mail of the API key's owner - per calendar month in UTC. It is the same counter the platform uses, so labels from the designer, print views and the API add up. The counter starts again at 0 on the 1st of each month at 00:00 UTC.

PlanLabels per monthBatch jobs and label enrichment
Developer (free)100No - 403
Starter10,000No - 403
Pro100,000Yes
Render top-upsAdded to the plan limit (bought under Billing in the platform).-

Everything else - design rendering as ZPL, PDF and PNG, the ZPL tools, printer and agent lists, quota and webhooks - works on every plan. POST /v1/batch/design/{designId} and POST /v1/enrich/zpl first check the plan of the key owner (GET /v1/quotaplan) and answer other plans with 403, or with 503 if the plan cannot be read. Status, output and pick list of a batch job (GET /v1/batch/jobs/…) are not plan-checked.

What is limited and what is counted

EndpointQuota checkRenders counted
POST /v1/zpl/render/design/{designId}Yes - 4291 per record (at least 1)
POST /v1/pdf/render/design/{designId}Yes - 4291 per page (= record, at least 1)
POST /v1/png/render/design/{designId}Yes - 4291
POST /v1/batch/design/{designId}Yes - 429 (Pro plan only)1 per record
POST /v1/tools/zpl-to-pdfNever blocked1 per label in the ZPL
POST /v1/tools/pdf-to-zplNever blocked1 per converted page
All other /v1/tools/{slug}Never blocked1 per call
POST /v1/enrich/zplNever blocked (Pro plan only)1 per enriched label
Batch job status, output and pick list; printers, agents, quota, webhooksNo0
  • The check runs before anything is rendered: if used + requested exceeds the limit, the request gets 429 and nothing is counted.
  • ZPL tools and label enrichment have no rate limit and keep working with the quota used up. Their renders still count - they can push the counter over the limit, and the next design render or batch job then gets 429.
  • Failed requests (status 400 and above) count nothing.
  • Counting is buffered per account: limit and usage are cached for 60 seconds, new renders count in memory at once and are written to the database every 5 seconds. GET /v1/quota and the 429 check therefore see new API renders immediately and labels rendered in the platform after at most 60 seconds; the platform dashboard can lag a few seconds behind.
  • API usage also appears in the API statistics on the dashboard.

Current numbers: GET /v1/quota.

Webhook callback

The endpoints in the index - all except Other endpoints - accept two optional query parameters. With them, zplCloud calls one of your stored webhooks when the request has finished - no polling, and for async batch jobs no open connection. The callback runs through the regular webhook system: signed with the webhook secret, retried on failure and listed in the delivery log in the platform.

Parameters

ParameterValueMeaning
webhookIdNumberID of a stored webhook (where to find it). It must be active and belong to the key owner: the key's company, or the owner's e-mail for keys without a company.
webhookResulttrue / false (default false)true puts the result - PDF, PNG, ZPL, JSON - into the callback. Requires webhookId.
curl -X POST "https://api.zplcloud.com/v1/tools/zpl-to-pdf?webhookId=12&webhookResult=true" \
  -H "X-API-Key: sk_zplcloud_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"zpl":"^XA^FO50,50^A0N,40,40^FDHello^FS^XZ","dpi":203}' \
  -o label.pdf -D -

HTTP/1.1 200 OK
Content-Type: application/pdf
X-ZplCloud-Request-Id: 3f0c9a52-7d1e-4b8a-9c61-2e5f4d8b7a10
X-ZplCloud-Webhook: queued

When the callback fires

  • After the request has finished - on success and on failure. Validation errors (400), missing Pro plan (403), not found (404), too large (413) and quota (429) are reported with status: "failed", the HTTP status and the error message.
  • Only after authentication and webhook validation. An invalid key (401), a sandbox key (403), an unknown or disabled webhookId or webhookResult=true without webhookId (400) are answered immediately, before any work, and trigger no callback. Neither do aborted requests (499) or a 503 during authentication.
  • Independent of the webhook's event subscriptions - webhookId addresses the webhook directly.
  • Response header X-ZplCloud-Webhook: queued = the callback is stored for delivery (usually delivered within seconds), failed = it could not be queued, e.g. because the webhook was disabled in the meantime. The header is only set when webhookId was given.
  • Async batch jobs ("async": true): the 202 response has no X-ZplCloud-Webhook header but a webhook object (id, includeResult, event). The callback api.batch.completed follows when the job has finished; its requestId is the jobId.

Payload

zplCloud sends a POST with a JSON body. Example: design rendered as PDF with webhookResult=true:

{
  "event": "api.design.pdf",
  "timestamp": "2026-09-11T10:15:02Z",
  "requestId": "3f0c9a52-7d1e-4b8a-9c61-2e5f4d8b7a10",
  "operation": "POST /v1/pdf/render/design/{designId}",
  "status": "succeeded",
  "httpStatus": 200,
  "durationMs": 184,
  "renders": 2,
  "summary": {
    "design": "shipping-label.42",
    "format": "pdf",
    "pages": 2,
    "bytes": 48211,
    "widthMm": 100,
    "heightMm": 150,
    "dpi": 203
  },
  "error": null,
  "resultIncluded": true,
  "result": {
    "contentType": "application/pdf",
    "encoding": "base64",
    "size": 48211,
    "data": "JVBERi0xLjcKJeLjz9MKMSAwIG9iago8PC9UeXBlL0NhdGFsb2cv..."
  }
}

Without webhookResult=true, and on every failure, there is no result: resultIncluded is false. Example: batch job rejected because the quota is used up:

{
  "event": "api.batch.completed",
  "timestamp": "2026-09-11T10:17:40Z",
  "requestId": "8b1d6c3e-2f4a-4e0b-9a7d-5c3e1f2a9b64",
  "operation": "POST /v1/batch/design/{designId}",
  "status": "failed",
  "httpStatus": 429,
  "durationMs": 36,
  "renders": 0,
  "summary": {
    "hint": "GET /v1/quota shows your current label quota. ...",
    "limit": 10000,
    "used": 9950,
    "requested": 500
  },
  "error": "Render limit reached (9950/10000 labels this month, this request needs 500).",
  "resultIncluded": false
}
FieldMeaning
eventEvent name (table below), also sent as header X-ZplCloud-Event.
timestampUTC time the request finished (ISO 8601, seconds).
requestIdSame GUID as the X-ZplCloud-Request-Id response header (async batch jobs: the jobId). Match callbacks to calls with it and drop duplicates.
operationHTTP method and route template, e.g. POST /v1/tools/zpl-to-pdf or GET /v1/batch/jobs/{jobId}.
statussucceeded (HTTP status below 400) or failed.
httpStatusStatus code of the API response. Async batch jobs: the status the job finished with (500 if it was aborted).
durationMsProcessing time in milliseconds.
rendersLabels counted for this call; always 0 on failure.
summaryShort, endpoint-specific facts (see event table). On failure: the extra error fields, e.g. { tool } or { hint, limit, used, requested }, or null.
errorError message on failure, otherwise null.
resultIncludedtrue when result is present.
resultOnly with webhookResult=true and success: { contentType, encoding, size, data }.
resultOmittedOnly with webhookResult=true when the result is too large: explains why result is missing.

Result encodings

encodingUsed fordata / size
base64File responses: PDF, PNG, JPG and the files from /v1/batch/jobs/{jobId}/output and /picklist (also when the output is ZPL).Base64 string of the file; size = file size in bytes.
utf-8Text responses: SVG from zpl-to-svg, raw ZPL from /v1/enrich/zpl?format=zpl.The text itself; size = UTF-8 bytes.
jsonJSON responses: converters, linter, analyser, lists, quota, batch jobs.The complete response object as in the HTTP response; size = serialized bytes.

Limit 10 MB. Larger results are left out: resultIncluded is false and resultOmitted says why - take the result from the API response instead (batch jobs: GET /v1/batch/jobs/{jobId}/output within one hour). For files the limit applies to the base64 size, so files up to about 7.5 MB fit.

Event names

EventEndpointsummary
api.design.pdfPOST /v1/pdf/render/design/{designId}design, format, pages, bytes, widthMm, heightMm, dpi
api.design.pngPOST /v1/png/render/design/{designId}design, format, record, bytes, widthMm, heightMm, dpi
api.tool.completedPOST /v1/tools/{slug}tool (the slug) plus tool-specific values, e.g. labels, bytes, zplBytes, errorCount
api.enrich.completedPOST /v1/enrich/zpllabels, fields, records, dpi, zplBytes
api.batch.completedPOST /v1/batch/design/{designId} (sync and async)jobId, jobStatus, design, labels, output, outputBytes, printer, printedLabels, printError, error
api.batch.statusGET /v1/batch/jobs/{jobId}as api.batch.completed
api.batch.outputGET /v1/batch/jobs/{jobId}/outputas api.batch.completed
api.batch.picklistGET /v1/batch/jobs/{jobId}/picklistas api.batch.completed
api.printers.listedGET /v1/printersscope, count, weblink, remote
api.company.printers.listedGET /v1/company/printersscope, count, weblink, remote, virtual
api.agents.listedGET /v1/agentsscope, count, online
api.company.agents.listedGET /v1/company/agentsscope, count, online
api.quota.checkedGET /v1/quotaplan, limit, used, remaining
api.webhooks.listedGET /v1/webhookscount

Delivery and retries

HeaderValue
Content-Typeapplication/json
User-Agentzplcloud-webhooks/1.0
X-ZplCloud-EventEvent name, e.g. api.design.pdf.
X-ZplCloud-DeliveryNumeric ID of the delivery; stays the same across retries.
X-ZplCloud-TimestampTime of this attempt, ISO 8601 UTC, e.g. 2026-09-11T10:15:02.4817731Z.
X-ZplCloud-SignatureLowercase hex HMAC-SHA256 of the raw body, keyed with the webhook secret. No sha256= prefix.
  • Your endpoint must answer with any 2xx within 10 seconds. Other status codes, timeouts and connection errors count as failed.
  • Retries after 1 minute, 5 minutes and 30 minutes; after the fourth failed attempt the delivery is marked failed. The body is identical in every attempt.
  • Only active webhooks receive deliveries; disabling a webhook pauses them.
  • Every attempt is listed in the webhook's delivery log in the platform (Webhooks → Log), where failed deliveries can be retried by hand.
  • A callback can arrive more than once, e.g. when your 2xx arrives after the timeout. Deduplicate on requestId.

Answer first, then process

Verify the signature, store the payload and return 2xx right away. Long work inside the request - printing, uploading the result, calling your ERP - risks the 10-second timeout and triggers retries of a callback you already handled.

Verifying the signature

Compute HMAC-SHA256 over the raw request body bytes - before any JSON parsing - with the webhook secret as key (shown in the platform under Webhooks), hex-encode it in lowercase and compare it with X-ZplCloud-Signature in constant time.

Node.js (Express):

import crypto from "node:crypto";
import express from "express";

const app = express();
const secret = process.env.ZPLCLOUD_WEBHOOK_SECRET;

// express.raw: the signature covers the exact bytes, so do not parse JSON first
app.post("/hooks/zplcloud", express.raw({ type: "application/json", limit: "16mb" }), (req, res) => {
  const expected = crypto.createHmac("sha256", secret).update(req.body).digest("hex");
  const received = req.get("X-ZplCloud-Signature") ?? "";
  const valid = received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!valid) return res.sendStatus(401);

  const payload = JSON.parse(req.body.toString("utf8"));
  res.sendStatus(204);        // answer within 10 s
  handleCallback(payload);    // your code: dedupe on payload.requestId, store payload.result
});

app.listen(3000);

C# (ASP.NET Core minimal API):

using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

app.MapPost("/hooks/zplcloud", async (HttpRequest request) =>
{
    using var buffer = new MemoryStream();
    await request.Body.CopyToAsync(buffer);   // raw bytes, before any JSON parsing
    var body = buffer.ToArray();

    var secret = Environment.GetEnvironmentVariable("ZPLCLOUD_WEBHOOK_SECRET")!;
    var expected = Convert.ToHexString(
        HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), body)).ToLowerInvariant();
    var received = request.Headers["X-ZplCloud-Signature"].ToString();
    if (!CryptographicOperations.FixedTimeEquals(
            Encoding.ASCII.GetBytes(expected), Encoding.ASCII.GetBytes(received)))
        return Results.Unauthorized();

    using var payload = JsonDocument.Parse(body);
    var requestId = payload.RootElement.GetProperty("requestId").GetString();
    // store the payload, process it in the background, dedupe on requestId
    return Results.NoContent();
});

Finding webhook IDs

  • Platform → Webhooks (/platform/webhooks): create a webhook with your receiver URL; every webhook shows its ID as a badge, e.g. ID 12. The secret for the signature is shown there too.
  • GET /v1/webhooks lists ID, URL, status and last delivery of all stored webhooks - see Printers, agents & quota.
  • The events a webhook is subscribed to do not matter for the callback.

Endpoint index

The interactive reference lists the whole public API, grouped into Rendering (Design rendering, Batch jobs, Label enrichment, Fonts), ZPL tools (Render ZPL, Convert to ZPL, Barcodes & GS1, Validate & analyse), Printing (Printers, Agents & watch folders, Remote printers, Virtual printers, Printer profiles & cookbook, Weblink certificates), Account (Quota & webhooks) and Integrations (DHL). This index is sorted by webhook support: all endpoints below accept webhookId and webhookResult, except those under Other endpoints. designId is <name>.<id>, e.g. shipping-label.42.

Rendering

EndpointBodyResultEvent
POST /v1/pdf/render/design/{designId}JSON array of records (optional, max 2,000, 5 MB)PDF, one page per recordapi.design.pdf
POST /v1/png/render/design/{designId}?record=0JSON array of records (optional)PNG of one labelapi.design.png

Batch jobs

EndpointPurposeEvent
POST /v1/batch/design/{designId}Records → ZPL or PDF, optional pick list, optional printing in blocks of 100 labels; sync 200 or "async": true202api.batch.completed
GET /v1/batch/jobs/{jobId}Job status, timings, print progressapi.batch.status
GET /v1/batch/jobs/{jobId}/outputGenerated ZPL or PDF (up to 20 MB, kept 1 hour)api.batch.output
GET /v1/batch/jobs/{jobId}/picklistPick list PDFapi.batch.picklist

Starting a job requires the Pro plan (403 otherwise); status, output and pick list of an existing job are not plan-checked.

Label enrichment

EndpointBodyResultEvent
POST /v1/enrich/zplCarrier or Amazon zpl, fields, records, dpiJSON { zpl, labels } or raw ZPL with ?format=zplapi.enrich.completed

Adds your own fields (SKU, bin location, PO) to every label without touching the original commands. Only available when label enrichment is enabled (404 otherwise) and on the Pro plan (403 otherwise).

ZPL tools

All tools are POST /v1/tools/{slug} with a JSON body; event api.tool.completed. Never blocked by the quota.

ToolMain inputResult
zpl-to-pdfzpl, dpi, widthMm, heightMmPDF with all labels
zpl-to-png
zpl-to-jpg
zpl-to-svg
zpl, dpi, widthMm, heightMmPNG, JPG or SVG of the first label
zpl-linterzpl, profile (amazon-fba)JSON: errors, warnings and hints
zpl-analyserzpl, networkMbitJSON: commands, label size, speed, darkness, time per label
html-to-zplpngBase64 (HTML rendered as PNG), dpi, widthMm, heightMmJSON with ZPL
pdf-to-zplpdfBase64, pages, scale, rotate, darknessJSON with ZPL (max 100 pages)
image-to-zplimageBase64 (PNG, JPG, GIF, BMP)JSON with ZPL and GRF
svg-to-zplsvg, dpi, sizeMmJSON with ZPL
epl2-to-zpl
dpl-to-zpl
tspl-to-zpl
codeJSON with ZPL
zpl-to-tspl
zpl-to-epl2
zpl-to-dpl
zpl-to-sbpl
zpl-to-cpcl
zpl-to-escpos
zpl-to-brother
zpl-to-pcl
zpl-to-easyplug
zpl-to-tpcl
zpl-to-jscript
zpl, dpi, compress (plus hex, cut, paper, model per language)Raw print job for TCP 9100
barcode-to-zplsymbology, dataJSON with ZPL
qr-code-to-zpltype (url, wifi, vcard …), fieldsJSON with ZPL
gs1-ai-128-to-zpl
gs1-ai-datamatrix-to-zpl
gs1-ai-qr-code-to-zpl
gs1-ai-databar-to-zpl
ai in bracket notation, e.g. (01)04006381333931(10)L-2026-0417JSON with ZPL and parsed AI elements
check-barcodeimageBase64 (photo or scan) or dataJSON: decoded and validated codes
dpi-calculatordpi, lengthMm, dots, fontPtJSON: conversions and reference table

Printers & agents

EndpointPurposeEvent
GET /v1/printersPrinters bound to the API keyapi.printers.listed
GET /v1/company/printersAll printers of the company: Weblink, remote, virtual - with target for batch jobsapi.company.printers.listed
GET /v1/agentszplCloud CLI agents connecting with the API keyapi.agents.listed
GET /v1/company/agentsAll agents of the company across all keysapi.company.agents.listed

Account & quota

EndpointPurposeEvent
GET /v1/quotaPlan, monthly label limit, used and remaining labels, periodapi.quota.checked
GET /v1/webhooksStored webhooks with their IDs (no secrets)api.webhooks.listed

Other endpoints

These endpoints do not take the webhook parameters. They are part of the interactive reference as well.

EndpointPurpose
POST /v1/zpl/render/design/{designId}Render a saved design to ZPL (max 500 records) - counts one render per record against the label quota, 429 when it is used up.
GET /v1/fonts
GET /v1/fonts/platform/{name}
System fonts; download a platform printer font such as ZPLCLOUD.TTF.
GET /v1/printer-profiles, /{id}Printer profiles with their ordered commands (used by zplcloud profiles apply).
GET /v1/zpl-cookbook, /{id}ZPL cookbook commands.
GET /v1/remote-printers
POST /v1/remote-printers/{id}/send, /file, /raw
Remote printers behind a zplCloud CLI agent: list, send ZPL/SGD, upload a ~DY file, print a job in another printer language byte-exact (finished or converted from ZPL - see printing the job).
GET /v1/virtual-printers
POST /v1/virtual-printers/{id}/print
GET /v1/virtual-printers/prints/{id}, .png
Virtual test printers - the only endpoints for sandbox keys.
POST /v1/weblink/certificates/generate
GET /v1/weblink/certificates/domains/…
Weblink certificates (used by zplcloud weblink setup).
POST /v1/integrations/dhl/stamp
GET /v1/integrations/dhl/stamps, /status
DHL Internetmarke - see Integrations.
POST /v1/print/folder
GET /v1/agents/folders
Watch-folder printing through an agent: ZPL, or jobs in other printer languages with language or base64 - see zplCloud CLI.

Quick start

1. Check the quota - also a quick test of the key:

curl https://api.zplcloud.com/v1/quota -H "X-API-Key: sk_zplcloud_YOUR_KEY"

2. Render ZPL as PNG - a ZPL tool, counts one render and is never blocked:

curl -X POST https://api.zplcloud.com/v1/tools/zpl-to-png \
  -H "X-API-Key: sk_zplcloud_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"zpl":"^XA^FO50,50^A0N,40,40^FDHello zplCloud^FS^XZ","dpi":203,"widthMm":75,"heightMm":50}' \
  -o label.png

3. Render a design as PDF and get called back - two records, two pages, two renders; webhook 12 receives api.design.pdf:

curl -X POST "https://api.zplcloud.com/v1/pdf/render/design/shipping-label.42?webhookId=12" \
  -u sk_zplcloud_YOUR_KEY: \
  -H "Content-Type: application/json" \
  -d '[{"sku":"A-1001","qty":"2"},{"sku":"A-1002","qty":"5"}]' \
  -o labels.pdf

Add &webhookResult=true to receive the PDF as base64 in the callback as well. Next steps: Rendering, ZPL tools, Printers, agents & quota.