PHP Quickstart
Install
Section titled “Install”composer require labelzoom/sdkPHP 8.1+. Built on PSR-18 and PSR-17, so it drops into Guzzle or Symfony HttpClient with no adapter — or uses its own cURL client if you would rather not choose.
Convert ZPL to PDF
Section titled “Convert ZPL to PDF”An API key is optional — without one you get the free tier.
require __DIR__ . '/vendor/autoload.php';
use LabelZoom\Sdk\LabelZoomClient;
$client = new LabelZoomClient(); // reads LABELZOOM_API_KEY; anonymous if unset
$result = $client->convert() ->fromZpl('^XA^FO50,50^ADN,36,20^FDLabelZoom^FS^XZ') ->toPdf() ->withLabelSize(widthInches: 4, heightInches: 6) ->withDpi(203) ->execute();
$result->save('label.pdf');$result->getBytes() is authoritative — five of the eleven targets are binary. getText()
decodes it with the response charset for the textual ones (zpl, xml, json).
Only options you actually set are sent, so a change to a server default reaches you without an SDK upgrade.
Passing the key explicitly, or forcing anonymous:
$explicit = new LabelZoomClient('lz_live_...');$fromEnv = new LabelZoomClient(); // reads LABELZOOM_API_KEY$anonymous = new LabelZoomClient(null); // forces anonymous, ignoring the envOmitting the argument and passing null mean different things on purpose: omitting it consults
the environment, null suppresses that fallback. The client is stateless once constructed —
create one per application, not one per request.
Converting a PDF to ZPL
Section titled “Converting a PDF to ZPL”use LabelZoom\Sdk\SourceFormat;
$result = $client->convert() ->fromFile(SourceFormat::Pdf, 'shipping-label.pdf') ->toZpl() ->withLabelSize(4, 6) ->withPdfPage(0) // 0-based; omit for every page ->execute();
$zpl = $result->getText();jpg and url are source-only on the server, and the type system says so: SourceFormat and
TargetFormat are separate enums, so there is no TargetFormat::Url to write. PHPStan rejects
it before it ever runs, rather than leaving you a 404 at runtime.
The printer languages epl, tspl and dpl are targets — fromPdf($bytes)->toEpl() is a real conversion. Read $result->getBytes() rather than the decoded text for those: EPL’s GW and TSPL’s BITMAP commands inline raw binary.
Bring your own HTTP client
Section titled “Bring your own HTTP client”The bundled cURL client is a default, not a dependency. Any PSR-18 implementation works, which is how you share a connection pool, proxy config, or middleware with the rest of your app:
$client = new LabelZoomClient( httpClient: new \GuzzleHttp\Client(['connect_timeout' => 2]),);Filling variable fields
Section titled “Filling variable fields”Each record produces one label:
$result = $client->convert() ->fromZpl($template) ->toPdf() ->withData([ ['name' => 'ACME Corp', 'sku' => '12345'], ['name' => 'Globex', 'sku' => '67890'], ]) ->execute(); // a 2-page PDF, with an API keyThe free tier returns the first label only, so this is a 1-page PDF until a key is configured. A single record may be passed on its own — it is wrapped rather than rejected.
Handling errors
Section titled “Handling errors”Every non-2xx throws a typed exception carrying the server’s message, the raw body, and the
X-LZ-Request-Id support handle:
use LabelZoom\Sdk\Exception\ForbiddenException;use LabelZoom\Sdk\Exception\LabelZoomException;
try { $result = $client->convert()->fromZpl($zpl)->toJson()->execute();} catch (ForbiddenException $e) { if ($e->isPaidFeature()) { // "JSON export is a paid feature" — the most common free-tier refusal. }} catch (LabelZoomException $e) { error_log($e->getStatus() . ': ' . $e->getMessage() . ' (' . $e->getRequestId() . ')');}429, 5xx and transport failures are retried automatically — 3 attempts, 1s/2s/4s with jitter,
honouring a longer Retry-After. Other 4xx responses throw immediately.
Calling the REST API directly
Section titled “Calling the REST API directly”The SDK is a wrapper over one HTTP endpoint; cURL works just as well.
$zpl = '^XA^FO50,50^ADN,36,20^FDLabelZoom^FS^XZ';
// The API key is optional — omit the Authorization header if it isn't set.$headers = ['Content-Type: text/plain', 'Accept: */*'];$apiKey = getenv('LABELZOOM_API_KEY');if ($apiKey !== false && $apiKey !== '') { $headers[] = 'Authorization: Bearer ' . $apiKey;}
$query = http_build_query(['label.width' => 4, 'label.height' => 6, 'dpi' => 203]);
$ch = curl_init();curl_setopt_array($ch, [ CURLOPT_URL => 'https://api.labelzoom.com/api/v2/convert/zpl/to/pdf?' . $query, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $zpl, CURLOPT_HTTPHEADER => $headers, CURLOPT_RETURNTRANSFER => true,]);
$body = curl_exec($ch);$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status !== 200) { throw new RuntimeException("LabelZoom returned {$status}: {$body}");}
file_put_contents('label.pdf', $body);echo "Wrote label.pdf\n";To convert a PDF to ZPL, post the file bytes with Content-Type: application/pdf and read the
response body as text.
Two things to watch when calling the API by hand, both of which the SDK handles for you:
-
Send
Accept: */*. The server’sproduceslist omitsimage/gif,image/bmpandimage/jpeg, so naming the target’s exact media type gets you a406from content negotiation before the handler runs. PHP’s cURL sends*/*by default — the risk is adding a “correct”Acceptheader yourself. -
Dot-notation query parameters only carry scalars.
?label.width=4&dpi=203is fine, but?data=[...]is rejected with a400. To fill variable fields over raw HTTP, send everything in a single JSON object instead:$query = 'params=' . rawurlencode(json_encode(['label' => ['width' => 4, 'height' => 6],'data' => [['name' => 'ACME Corp', 'sku' => '12345']],]));
- Reuse one client across conversions for connection pooling; see rate limits for per-plan throughput.
- All endpoint patterns and parameters: quickstart · Swagger reference.
- Source, issues, and the shared conformance suite: labelzoom/labelzoom-sdk.