Ruby Quickstart
Install
Section titled “Install”gem install labelzoomgem "labelzoom", "~> 1.0"Ruby 3.1+, no runtime dependencies — net/http, json, uri and openssl are all
standard library, so the gem drops into a Rails app that already pins its own HTTP stack
without an argument.
Convert ZPL to PDF
Section titled “Convert ZPL to PDF”An API key is optional — without one you get the free tier.
require "labelzoom"
client = LabelZoom::Client.new # reads LABELZOOM_API_KEY; anonymous if unset
result = client.convert( :zpl, :pdf, "^XA^FO50,50^ADN,36,20^FDLabelZoom^FS^XZ", label: { width: 4, height: 6 }, # inches dpi: 203)
result.save("label.pdf")result.bytes is authoritative — five of the eleven targets are binary. result.text 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 a gem upgrade.
Options are nested keyword arguments
Section titled “Options are nested keyword arguments”Source, target and body are positional; everything else is a keyword argument, with nesting
expressed as a hash rather than flattened into label_width:
client.convert( :zpl, :png, zpl, dpi: 300, rotation: 90, # must be a multiple of 90 color_mode: "GRAYSCALE", watermark: false, # an explicit false IS sent label: { width: 4.0, height: 6.0 }, # inches pdf: { conversion_mode: "IMAGE", page_number: 0 }, zpl: { commands_to_ignore: ["^PQ"], image_compression: "Z64" })A misspelled key raises rather than vanishing. Ruby has no compiler to catch
label: { widht: 4 }, and the server ignores keys it does not recognise, so a silent drop would
hand you a wrong label and no signal.
Configuring the client
Section titled “Configuring the client”explicit = LabelZoom::Client.new(api_key: "lz_live_...")from_env = LabelZoom::Client.new # reads LABELZOOM_API_KEYanonymous = LabelZoom::Client.new(api_key: nil) # ignores the envOmitting api_key: and passing nil mean different things on purpose: omitting it consults the
environment, nil (or "") 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”result = client.convert( :pdf, :zpl, File.binread("shipping-label.pdf"), label: { width: 4, height: 6 }, pdf: { page_number: 0 } # 0-based; omit for every page)
zpl = result.text:jpg and :url are source-only on the server. Ruby has no compile step, so the SDK enforces
it at the call: passing either as a target raises LabelZoom::ValidationError before any request
goes out.
The printer languages :epl, :tspl and :dpl are targets — :pdf → :epl is a real
conversion. Read result.bytes rather than result.text for those: EPL’s GW and TSPL’s
BITMAP commands inline raw binary.
Filling variable fields
Section titled “Filling variable fields”Each record produces one label:
result = client.convert( :zpl, :pdf, template, data: [ { name: "ACME Corp", sku: "12345" }, { name: "Globex", sku: "67890" } ]) # 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 hash may be passed on its own — it is wrapped rather than rejected.
Handling errors
Section titled “Handling errors”Every non-2xx raises a typed error carrying the status, the message, the raw body, and the
X-LZ-Request-Id support handle:
begin result = client.convert(:zpl, :json, zpl)rescue LabelZoom::ForbiddenError => e # "JSON export is a paid feature" — the most common free-tier refusal. warn "paywall" if e.paid_feature?rescue LabelZoom::APIError => e warn "request #{e.request_id} failed with #{e.status}: #{e.message}"endBadRequestError, UnauthorizedError, ForbiddenError, NotFoundError,
PayloadTooLargeError, RateLimitedError and ServerError all descend from
LabelZoom::APIError, so one rescue catches the lot.
LabelZoom::ValidationError deliberately does not — it descends from ArgumentError. It
reports a request rejected locally, before any network call, which is a bug in the calling code
rather than a server response, so rescuing APIError for fallback behaviour will not swallow it.
429, 5xx and transport failures are retried automatically — 3 attempts, 1s/2s/4s with jitter,
honouring a longer Retry-After. Other 4xx responses raise immediately.
Testing your own code
Section titled “Testing your own code”The sleeper and the environment lookup are both injectable, so a test never sleeps and never picks up a developer’s real key:
slept = []client = LabelZoom::Client.new( sleeper: ->(seconds) { slept << seconds }, jitter: false, env: {})Stub HTTP with WebMock, which is what the gem’s own suite
uses — it intercepts at the Net::HTTP layer, so the real request-construction path is
exercised rather than bypassed.
Calling the REST API directly
Section titled “Calling the REST API directly”The SDK is a wrapper over one HTTP endpoint; net/http works just as well.
require "json"require "net/http"require "uri"
zpl = "^XA^FO50,50^ADN,36,20^FDLabelZoom^FS^XZ"
uri = URI("https://api.labelzoom.com/api/v2/convert/zpl/to/pdf")uri.query = URI.encode_www_form( "params" => JSON.generate({ "label" => { "width" => 4, "height" => 6 }, "dpi" => 203 }))
request = Net::HTTP::Post.new(uri)request["Content-Type"] = "text/plain"request["Accept"] = "*/*"api_key = ENV["LABELZOOM_API_KEY"]request["Authorization"] = "Bearer #{api_key}" if api_key && !api_key.empty?request.body = zpl
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }raise "LabelZoom returned #{response.code}: #{response.body}" unless response.code == "200"
File.binwrite("label.pdf", response.body)Two things to watch when calling the API by hand, both of which the SDK handles for you:
Accept: */*is the safe default. Naming the target’s exact media type works, but*/*is the one value valid for every target against every deployed server version — older servers answer an exactimage/gif,image/bmporimage/jpegwith a406.- Prefer a single
paramsobject. Dot notation carries JSON as well —?label.width=4&dpi=203and?data=[{"sku":"A-100"}]both work — but one serialization path with no per-field exceptions is the habit that keeps working as you add options.
Note File.binwrite, not File.write — a PDF is binary, and writing it in text mode corrupts
it on a non-UTF-8 default external encoding.
- Reuse one client across conversions for connection pooling; see rate limits for per-plan throughput.
- All endpoint patterns and parameters: quickstart · Swagger reference.
- Package page: rubygems.org/gems/labelzoom.
- Source, issues, and the shared conformance suite: labelzoom/labelzoom-sdk.