Skip to content

PHP Quickstart

Terminal window
composer require labelzoom/sdk

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

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 env

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

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.

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]),
);

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 key

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

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.

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’s produces list omits image/gif, image/bmp and image/jpeg, so naming the target’s exact media type gets you a 406 from content negotiation before the handler runs. PHP’s cURL sends */* by default — the risk is adding a “correct” Accept header yourself.

  • Dot-notation query parameters only carry scalars. ?label.width=4&dpi=203 is fine, but ?data=[...] is rejected with a 400. 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']],
    ]));