Zum Inhalt springen

Convert ZPL to PDF in C#

To convert ZPL to PDF in C#, post the ZPL as StringContent with HttpClient to the OpenZPL render endpoint, ask for application/pdf, and copy the response stream to a file. Reuse one HttpClient for the whole application and check the status code before saving.

Aktualisiert am 2026-10-20 · Renders made with the OpenZPL engine

What you need

  • Any supported .NET (6 or later). HttpClient is part of the base library; no NuGet package is needed.
  • The ZPL as a string, the label size in inches and the printer density (8dpmm = 203 dpi).

The code

HttpClient is meant to be created once and reused: a new client per label exhausts sockets under load. Ask for the headers first (ResponseHeadersRead) so the PDF is copied to the file as it arrives instead of being buffered.

using System.Net;
using System.Net.Http.Headers;
using System.Text;

public static class LabelApi
{
    static readonly HttpClient Http = new()
    {
        BaseAddress = new Uri("https://api.openzpl.com/"),
        Timeout = TimeSpan.FromSeconds(30),
    };

    public static async Task<int> ZplToPdfAsync(string zpl, string path,
        int dpmm = 8, string size = "4x6", string? apiKey = null, int retries = 3)
    {
        for (var attempt = 0; ; attempt++)
        {
            using var req = new HttpRequestMessage(HttpMethod.Post, $"v1/printers/{dpmm}dpmm/labels/{size}/")
            {
                Content = new StringContent(zpl, Encoding.UTF8, "text/plain"),
            };
            req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/pdf"));
            if (apiKey is not null) req.Headers.Add("X-API-Key", apiKey);

            using var res = await Http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
            if ((res.StatusCode == HttpStatusCode.TooManyRequests ||
                 res.StatusCode == HttpStatusCode.ServiceUnavailable) && attempt < retries)
            {
                var wait = res.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(1);
                await Task.Delay(wait);
                continue;
            }
            if (!res.IsSuccessStatusCode)
                throw new HttpRequestException($"HTTP {(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");

            await using (var file = File.Create(path))
                await res.Content.CopyToAsync(file);
            return int.Parse(res.Headers.GetValues("X-Total-Count").First());
        }
    }
}

// var count = await LabelApi.ZplToPdfAsync(File.ReadAllText("label.zpl"), "label.pdf");

The label in this example:

Return label with a reversed RETURN LABEL header, the RMA number, the reason and a Code 128 barcode
4 × 2 in label at 203 dpi (8 dpmm) · 812 × 406 px · Open in the viewer
^XA
^FO30,30^GB752,70,70^FS
^FO50,48^A0N,40,40^FR^FDRETURN LABEL^FS
^FO30,120^A0N,26,26^FDRMA 77 2026 0193^FS
^FO30,155^A0N,26,26^FDReason: wrong size^FS
^BY2,3,100
^FO30,200^BCN,100,Y,N,N^FDRMA7720260193^FS
^XZ

Notes for .NET

  • In ASP.NET Core, register the client with IHttpClientFactory (services.AddHttpClient) instead of a static field; the request code stays the same.
  • Retry-After arrives as a number of seconds, which .NET exposes as RetryAfter.Delta. The daily limit sets it to the seconds left until midnight UTC: cap the delay in a background job.
  • The API reads the body as text. StringContent with UTF-8 keeps accents intact if the label starts with ^CI28.
  • To return the PDF from a controller, stream it with File(stream, "application/pdf") rather than loading it into a byte array.

Fehler

StatusWhat it meansWhat to do
400A parameter in the URL is wrong, or a barcode's data is invalid.Read the text after ERROR: and fix the request. Don't retry it unchanged.
404The ZPL has no label, or the label index is out of range. X-Total-Count says how many labels there are.Check that the ZPL has complete ^XA … ^XZ blocks, or ask for an index below X-Total-Count.
406The Accept header asks for a format the API doesn't produce.Send image/png or application/pdf.
413Der Body ist über 1 MB oder das ZPL erzeugt mehr als 50 Etiketten.Split the job into several requests.
429A limit of your plan was hit: requests per second, or labels per UTC day.Wait the seconds in Retry-After, then retry.
503The label took more than 10 seconds to render, or the service is at capacity.Retry once after Retry-After; if it repeats, lower the density or split the label.

Grenzen

  • Endpoint: POST https://api.openzpl.com/v1/printers/8dpmm/labels/4x6/ with the ZPL as the request body. The density is 6dpmm, 8dpmm, 12dpmm or 24dpmm; the size is in inches, up to 15 per side.
  • Accept: application/pdf without a label index returns every label, one per page. Add an index (.../4x6/0/) to get a single label.
  • Free plan: no key, 3 requests per second and 5,000 labels per day per IP address. Every label counts, ^PQ copies and PDF pages included. See pricing.
  • Per request: up to 50 labels and 1 MB of ZPL.

Fragen

Is there a .NET library to convert ZPL to PDF?

You can use one, but the API needs nothing beyond HttpClient: it returns the finished PDF. There is no native renderer to install or license.

Can I print the PDF from C# afterwards?

Yes, with any PDF printing component, on any printer. To print ZPL on a Zebra printer you would send the ZPL itself, not the PDF.

Sources