Use the SendFileToPrinter API for Your Cloud-Based Printing Needs

Use the SendFileToPrinter API for Your Cloud-Based Printing Needs

Why use the SendFileToPrinter API?

Zebra Data Services has recently released a cloud-based REST API for Zebra Print DNA printers featuring our powerful Link-OS operating system. It is called SendFileToPrinter API. This is good news for developers who want to take advantage of cloud-based printing without worrying about how to connect the printers securely to the cloud. The SendFileToPrinter API allows developers to send content quickly, easily and securely in one file to a Print DNA printer via the powerful Zebra Data Services platform*.

Here are some of the advantages of the SendFileToPrinter API.

  1. The SendFileToPrinter is a REST API that is language & platform agnostic, which means it can be used with any programming languages on any platform, because it is simply a matter of a HTTPS Multipart POST request. You simply choose the programming language and platform that you are familiar or comfortable with when using this API.
  2. The connection from your printer to Zebra Data Services cloud is secured through SSL/TLS. There is no need to reinvent the wheel to create a secured connection yourself. You can be assured that your printer is safely and securely connected to the Zebra Data Services cloud.
  3. Since it is cloud-based, you can print labels and receipts or send content, up to 10 MB in size, in a file to any connected Print DNA printer. There is no need to tether a printer through USB or Bluetooth to a computer or a mobile device. This provides great flexibility for your application.

What can the SendFileToPrinter API do?

This SendFileToPrinter API is unidirectional. It is a simple, yet very powerful API. It can be used to send a wide variety of content to the printer, including commands found in the ZPL Programming Guide*. For example, you can use this API to print labels or receipts by sending the ZPL format commands in a file format to the printer. You can use this API to download objects, such as fonts, graphics, or Wi-Fi® certificates, in a file format to the printer. This API can also be used to configure the printer by sending Set-Get-Do (SGD) configuration commands or JSON configuration strings in a file format to the printer. You can send any allowed content to the printer through this API. You have the option to send the content to one or multiple printers in one API call.

If you are familiar with Zebra ZPL format commands, you can create the label or receipt with ZPL and send the ZPL content in a file straight away with this API to a printer to print. If you are unfamiliar with ZPL, then you can use ZebraDesigner for Developers 3, a free tool, to create your design and generate the ZPL for you.

Since the SendFileToPrinter is a unidirectional API, do not expect to use this API to retrieve anything out from the printer. For example, you cannot retrieve the printer settings with this API by sending the related SGD or JSON commands, because this API does not return such content. However, the caller of this API gets acknowledged on whether the API call is successful or not through the response each time.

How to get started?

To use the SendFileToPrinter API, you need an API key and a Print DNA printer registered with the Zebra Data Services cloud platform. Here are the How-To articles to help you get started.

Apply for Zebra Developer Account and API Key

Getting Started with Zebra Data Services – A step-by-step guide on how to open a developer account, how to add API packages to your account, how to add an app and generate the API key, etc. on the Zebra Data Services developer portal. 


  1. To gain access to the SendFileToPrinter API, you'll need to add a SendFileToPrinter package (Free or PPC) and select the package in your account when you create an app to generate the API key.
  2. Using the same API key for both Free and Pay Per Call (PPC) packages are treated as an API call to both packages at the same time. It is recommended to use a separate API key for the SendFileToPrinter (Free) and SendFileToPrinter (PPC) packages.

Printer Setup

Printer Setup Guide – A step-by-step guide on how to configure the network connection, the Weblink endpoint to the Zebra Data Services platform, and how to get the tools required to send the JSON configuration file to a Print DNA printer. For more details about Weblink, refer to the Using Weblink section in ZPL Programming Guide

Use the following JSON string for the Weblink configuration of your Print DNA printer. Replace the value of the query parameter r (The r must be in lowercase) with the printer enrollment code, an auto-generated code by clicking on Add Device button on My Devices page at To access My Devices page, you'll need to log into your developer account.


The above JSON string can be sent to the printer as a file by using the Printer Setup Utilities for Windows, Android or iOS.


  1. There should be no space between the braces in the above JSON string, i.e. no spaces in "{}{".
  2. You can have two Weblinks configured at the same time on a printer, i.e. weblink.ip.conn1.location and weblink.ip.conn2.location. However, they cannot point to the same Weblink URL. If they point to the same Weblink URL, they will cancel each other’s connection and cause a ping-pong effect. Refer to the Using Weblink section in ZPL Programming Guide for additional info.
  3. Zebra Data Services only supports one Weblink connection per Print DNA printer at a time. You can only use one Weblink on the printer to connect to Zebra Data Services. The other Weblink can be used for other services.
  4. Make sure the previous Weblink configuration is not unintentionally overwritten. The following commands can be used to check if the Weblink configurations are already in use, before overwriting either one of them.
! U1 getvar "weblink.ip.conn1.location"
! U1 getvar "weblink.ip.conn2.location"

Unregister and disconnect the printer


By passing an empty string ("") to the weblink.ip.conn1.location, the above JSON configuration string will remove the Weblink URL and disconnect the Weblink connection from weblink.ip.conn1.location. You can do the same for weblink.ip.conn2.location, if it is configured for connecting to the Zebra Data Services cloud platform. Note: When removing or reconfiguring a Weblink URL, please be cautious to not impact or disrupt other applications use of the Weblink connection.

Test Print

Once you complete the steps in Getting Started with Zebra Data Services and Printer Setup Guide, you can send the following HTTPS request in cURL to the printer. It will print out “Hello World” on a label, assuming you have the “^XA ^FO50,50^ADN,36,20^FDHello World^FS ^XZ” ZPL string (without the double quotes) stored in a file called HelloWorld.txt. Note: You'll need to replace the API key and the tenant account number in the request header and the printer’s serial number of the sn key-value pair in the request body with your values in order to see the action. The tenant account number can be retrieved from the Tenant Service.

$ curl -H "apikey: yourApiKey" -H "tenant: yourTenantAccountNumber" \
       -F "sn=printerSerialNumber" -F "zpl_file=@HelloWorld.txt" \

Note: If you want to send the HelloWorld.txt file to more than one printer, you simply add additional sn key-value pairs in the request body. The following cURL, for example, will print “Hello World” from two printers.

$ curl -H "apikey: yourApiKey" -H "tenant: yourTenantAccountNumber" \
       -F "sn=printerSerialNumber_1" \
       -F "sn=printerSerialNumber_2" -F "zpl_file=@HelloWorld.txt" \

If you prefer, you can also use Postman instead of cURL to do the test print.


  1. The maximum size of the file supported by the SendFileToPrinter API is 10MB at this moment. If the file size is larger than 10MB, unexpected error may occur.
  2. Only one file can be sent at a time. If multiple files are specified with multiple zpl_file key-value pairs in the request body, only one file is accepted and sent. All the other files in the request will be ignored.
  3. The file content of ZPL label, receipt format, or downloadable object must be complete. It cannot be divided into partial formats in multiple files to send, i.e. concatenating multiple files is not supported.
  4. It is recommended not to call the API to send file or content to the printer while the printer is updating its operating system, as the delivery of the file or the content is not guaranteed during the printer operating system update.

*Max file size is 10 MB

Steven Si

Here are a few examples of how to use this API

  • JavaScript: 

PrintFromCloud - A JavaScript example demonstrates how to use the SendFileToPrinter API to print labels and receipts from anywhere to any Link-OS printers connected to the Zebra Data Services cloud platform.

  • Java:

Use the Zebra SendFileToPrinter Multipart BodyPublisher (, which is based on the package,  for your convenience. The following code snippet shows how to use

// Create URI from the URL of SendFileToPrinter API
URI uri = URI.create("");

String sn = "XXZJJ174600974"; // Printer serial number example
String apikey = "EvqdePLp1WMpK8ZTqrAGDAGqCzs83AGn"; // API Key example
String tenant = "695315b271dd374c76fad074e6b0f8cf"; // Tenant ID example

// Example ZPL file containing - "^XA ^FO50,50^ADN,36,20^FDHello World^FS ^XZ"
Path zplFilePath = Paths.get("C:\\Users\\john\\HelloWorld.txt");

ZebraSftpMpBodyPublisher publisher = new ZebraSftpMpBodyPublisher().addSn(sn).setZplFilePath(zplFilePath);

HttpRequest request = HttpRequest.newBuilder().uri(uri).header("apikey", apikey).header("tenant", tenant).header("Content-Type", "multipart/form-data; boundary="
            + publisher.getBoundary()).POST(;

HttpClient httpclient = HttpClient.newBuilder().build();
HttpResponse response = httpclient.send(request, BodyHandlers.ofString());


Here is the

package com.zebra.sendfiletoprinter.sample;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID;

public class ZebraSftpMpBodyPublisher {

    private String boundary = UUID.randomUUID().toString();

    private List<String> serialNumberList = new ArrayList<String>(); // At least to have one printer SN
    private Path zplFilePath = null; // Must have one zplFilePath to build the multipart request body.

    public HttpRequest.BodyPublisher build() throws IOException {
        if (serialNumberList.size() == 0) {
            throw new IllegalStateException("Must have at least one printer serial number to build multipart request body.");
        if (zplFilePath == null) {
            throw new IllegalStateException("Must have a ZPL file to build multipart request body.");
        return HttpRequest.BodyPublishers.ofByteArrays(ZebraSftpPartsIterator::new);

    public String getBoundary() {
        return boundary;

    // Add a printer serial number. You can have more than one serial numbers.
    public ZebraSftpMpBodyPublisher addSn(String serialNumber) {
        return this;

    public ZebraSftpMpBodyPublisher setZplFilePath(Path zplFilePath) {
        this.zplFilePath = zplFilePath;
        return this;

    class ZebraSftpPartsIterator implements Iterator<byte[]> {

        private Iterator<String> snIter;
        private InputStream zplFileInput;

        private boolean zpl_file_content_done = false;
        private boolean final_boundary_done = false;

        private boolean done;
        private byte[] next;

        ZebraSftpPartsIterator() {
            snIter = serialNumberList.iterator();

        public boolean hasNext() {
            if (done)
                return false;
            if (next != null)
                return true;
            try {
                next = calcuNextPart();
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            if (next == null) {
                done = true;
                return false;
            return true;

        public byte[] next() {
            if (!hasNext())
                throw new NoSuchElementException();
            byte[] tmp = next;
            next = null;
            return tmp;

        private byte[] calcuNextPart() throws IOException {

            // First, get the parts for all the serial numbers.
            if (snIter.hasNext()) {
                String snBody = "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"sn\"" + "\r\n\r\n" + + "\r\n";
                return snBody.getBytes(StandardCharsets.UTF_8);

            } else if (!zpl_file_content_done) {
                // Done with SN. Then get the part for ZPL file.
                if (zplFileInput == null) {
                    // Open the file input stream
                    zplFileInput = Files.newInputStream(zplFilePath);

                    // Create the header for ZPL file part
                    String contentType = Files.probeContentType(zplFilePath);
                    String zplFileBody = "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"zpl_file\"; filename=\"" + zplFilePath.getFileName().toString() + "\"" + "\r\n"
                            + "Content-Type: " + contentType + "\r\n\r\n";
                    return zplFileBody.getBytes(StandardCharsets.UTF_8);
                } else {
                    // Read the file content
                    byte[] bytes = zplFileInput.readAllBytes();
                    zplFileInput = null;
                    byte[] actual = new byte[bytes.length + 2];
                    byte[] newline = "\r\n".getBytes(StandardCharsets.UTF_8); // Ending for the file content part.
                    System.arraycopy(bytes, 0, actual, 0, bytes.length);
                    System.arraycopy(newline, 0, actual, bytes.length, newline.length);
                    zpl_file_content_done = true;
                    return actual;
            } else if (!final_boundary_done) {
                // Add the final boundary
                final_boundary_done = true;
                String finalBoundary = "--" + boundary + "--";
                return finalBoundary.getBytes(StandardCharsets.UTF_8);
            } else {
                // All done
                return null;

Use the above to ensure that the multipart body complies with the format below, so that it can be parsed correct by the backend.

Content-Disposition: form-data; name="sn"

Content-Disposition: form-data; name="zpl_file"; filename="HelloWorld.txt"
Content-Type: text/plain

^XA^FO50,50^ADN,36,20^FDHello World!^FS^XZ

Jose Manuel Sanchez

Could you, please, provide an example usage for .NET in C#?

Sing Yiu Cheung

Thanks Steven,

This blog is very helpful.
And we would love to use the sendFileToPrinter API.
One issue we encounter is the CORS error if we try to call the API from a web app (i.e. React).
Any suggested solution ?
By any chance we can set Access-Control-Allow-Origin: * on the API server side ?

Best regards,

Steven Si

Hi Sing, I think that our server has already allowed the cross origin access for SendFileToPrinter API, because we understand that it's an API and could be used from anywhere. This is the first time we've heard a CORS issue with this API. Would this have something to do with your browser or something else? Would you share the error msg?

Sing Yiu Cheung

Hi Steven,
Thanks a lot for the follow up.
Before we circle back on the CORS issue, we have a more immediate issue on the SendFileToPrinter API.

It was all working perfectly yesterday (4-29-2021), where I had tested it all day long.
It stopped working since this morning (4-30-2021), failed on calling the API from terminal or browser (
I had confirmed the printer is connected to internet as expected.

Here is an example curl output:

curl -v -X POST "" -H "accept: text/plain" -H "apikey: XXX" -H "tenant: XXX" -H "Content-Type: multipart/form-data" -F "sn=D0J200311057" -F "zpl_file=@ZPL_test10.txt;type=text/plain"
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying
* Connected to ( port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
* subject: C=US; ST=Illinois; L=Lincolnshire; O=Zebra Technologies Corporation; OU=ES - API Management;
* start date: Jun 2 00:00:00 2019 GMT
* expire date: Jun 1 12:00:00 2021 GMT
* subjectAltName: host "" matched cert's ""
* issuer: C=US; O=DigiCert Inc;; CN=Thawte RSA CA 2018
* SSL certificate verify ok.
> POST /v2/devices/printers/send HTTP/1.1
> Host:
> User-Agent: curl/7.64.1
> accept: text/plain
> apikey: HR972nQ5GG4uRUzVtJQwozC6xJbTHDYq
> tenant: 22d8789fe3db064c178321201587aa44
> Content-Length: 534
> Content-Type: multipart/form-data; boundary=------------------------6243d78013ad68ad
* We are completely uploaded and fine
< HTTP/1.1 302
< Date: Fri, 30 Apr 2021 17:24:28 GMT
< Content-Length: 0
< Connection: keep-alive
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Set-Cookie: SESSION=OGE0N2QzYTItMTJkYy00ZjJmLThjNDctMDFjYmZkZjkyYTM3;; Path=/; Secure; HttpOnly; SameSite=Lax; Secure
< Location:
< Strict-Transport-Security: max-age=15768000
< Set-Cookie: SERVERID=pod-gatekeeper-5dd64cbdbf-68cpz; path=/
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Headers: access-control-allow-origin, origin, x-requested-with, accept, Cache-Control, apikey, tenant, username, password, encoded, content-type
< Access-Control-Max-Age: 3628800
< Access-Control-Allow-Methods: GET, PUT, POST, DELETE
* Connection #0 to host left intact
* Closing connection 0

It would be great if you can help troubleshoot and restore the API to working status.
Thanks again.


Sing Yiu Cheung

Hi Steven,
I am happy to report that the API is now back to normal. Any insight regarding the outage ?
Any reference if we want to build our own SendFileToPrinter server, such that we can fall back onto a 2nd server in case the Zebra hosted one is out of order ?

Thanks a lot.

Steven Si

There was a service outage due to a side effect caused by the repository migration from one server to another. It was an unexpected onetime outage. We will look into this further and will prevent this from happening again in the future. Regarding your question, the SendFileToPrinter API is already built with a redundant architecture for fault tolerance. Today's outage is a very unique case caused by the migration.

Hussain Hussain

I am getting
{"error":"invalid_token","error_description":"Cannot convert access token to JSON"}
any help?