ZPL Web Service

[support@labelary.com ~]$ curlie -v \
  POST http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/ \
  --data "^xa^cfa,50^fo100,100^fdHello World^fs^xz"

> POST /v1/printers/8dpmm/labels/4x6/0/ HTTP/1.1
> Host: api.labelary.com
> Accept: */*
> Content-Length: 40
> Content-Type: application/x-www-form-urlencoded
>
> ^xa^cfa,50^fo100,100^fdHello World^fs^xz

< HTTP/1.1 200 OK
< Content-Type: image/png
< Transfer-Encoding: chunked
< Connection: keep-alive
< X-Total-Count: 1
<
< +-----------------------------------------------+
< | NOTE: binary image data not shown in terminal |
< +-----------------------------------------------+

1. Introduction

The Labelary ZPL rendering engine is available as an online service, and can be invoked via a simple, easy-to-use RESTful API:

GET http://api.labelary.com/v1/printers/{dpmm}/labels/{width}x{height}/{index}/{zpl}

By default the service returns PNG images, but a variety of output formats are available:

  • PNG (requested by sending an Accept: image/png request header, or by omitting the Accept request header entirely)
  • PDF (requested by sending an Accept: application/pdf request header)
  • IPL (requested by sending an Accept: application/ipl request header)
  • EPL (requested by sending an Accept: application/epl request header)
  • DPL (requested by sending an Accept: application/dpl request header)
  • JSON (requested by sending an Accept: application/json request header, useful for data extraction)

You can also use POST instead of GET by moving the ZPL out of the URL and into the POST message body (see below for examples).

The POST HTTP method is useful when:

  1. Your ZPL is very large (since URLs are limited to roughly 3,000 characters), or
  2. You are running into character encoding issues, or
  3. Your ZPL contains embedded binary data, or
  4. Your ZPL contains sensitive information (since URLs are often logged by proxies and other intermediaries)

2. Parameters

dpmm
The desired print density, in dots per millimeter.
Valid values are 6dpmm, 8dpmm, 12dpmm, and 24dpmm. See your printer's documentation for more information.
width
The label width, in inches. Any numeric value may be used.
height
The label height, in inches. Any numeric value may be used.
index
The label index (base 0).
Some ZPL code will generate multiple labels, and this parameter can be used to access these different labels. In general though, the value of this parameter will be 0 (zero).
Note that this parameter is optional when requesting PDF documents. If not specified, the resultant PDF document will contain all labels (one label per page).
zpl
The ZPL code to render.
Note that if you are using the GET HTTP method and the ZPL contains any hashes (#), they should be encoded (%23) in order to avoid parts of the ZPL being incorrectly interpreted as URL fragments.

3. Limits

As a shared service, the Labelary API incorporates a number of usage limits which ensure that no single user can negatively impact the workloads of other users:

  • Maximum 5 requests per second per client. Additional requests result in a HTTP 429 (Too Many Requests) error.
  • Maximum 50 labels per request. Additional labels result in a HTTP 413 (Payload Too Large) error. See the FAQ for details.
  • Maximum label size of 15 x 15 inches. Larger labels result in a HTTP 400 (Bad Request) error.
  • Maximum 10 MB image buffer when generating PNG files. Images requiring a larger memory buffer result in a HTTP 400 (Bad Request) error. See the FAQ for details.
  • Maximum embedded object size of 5MB, for e.g. ~DU and ~DY. Larger embedded objects result in a HTTP 400 (Bad Request) error.
  • Maximum embedded image dimensions of 2,000 x 2,000 pixels, for e.g. ~DG and ~DY. Larger embedded images result in a HTTP 400 (Bad Request) error.
  • Maximum 5 MB printer memory used for fonts, images and other files. Exceeding this limit results in a HTTP 400 (Bad Request) error. See the FAQ for details.

The image conversion service (image → ZPL) also has the following limits:

  • Maximum input image file size of 200 KB. Larger files result in a HTTP 400 (Bad Request) error.
  • Maximum input image dimensions of 2,000 x 2,000 pixels. Larger image sizes result in a HTTP 400 (Bad Request) error.

Note that these limits may be changed as needed in order to ensure smooth operation of the service for all users.

If these limits are too restrictive for your intended use, you may want to consider licensing Labelary for private on-premise use.

4. Examples

The code snippets and examples below are intended to help you start consuming the Labelary API quickly, regardless of your client-side technology stack. If you have a code sample that uses a different technology stack, and you'd like to share it with other Labelary users, feel free to email us.

4.1. Live Examples

Some live examples that use GET requests to convert the ZPL encoded in the URLs into PNG images:

4.2. curl Examples

Using the GET method:

curl --get http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/ --data-urlencode "^xa^cfa,50^fo100,100^fdHello World^fs^xz" > label.png

Using the POST method (with application/x-www-form-urlencoded content):

curl --request POST http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/ --data "^xa^cfa,50^fo100,100^fdHello World^fs^xz" > label.png

Using the POST method (with multipart/form-data content):

curl --request POST http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/ --form file=@label.zpl > label.png

Using the POST method (with multipart/form-data content), requesting a PDF file instead of a PNG image:

curl --request POST http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/ --form file=@label.zpl --header "Accept: application/pdf" > label.pdf

4.3. Postman Examples

You can also test the Labelary API by importing this request collection into Postman and running any of the requests therein.

4.4. PowerShell Examples

Using the POST method (with application/x-www-form-urlencoded content) to request a PNG image:

Invoke-RestMethod `
-Method Post `
-Uri http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/ `
-ContentType "application/x-www-form-urlencoded" `
-InFile label.zpl `
-OutFile label.png

Using the POST method (with application/x-www-form-urlencoded content) to request a PDF file:

Invoke-RestMethod `
-Method Post `
-Uri http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/ `
-ContentType "application/x-www-form-urlencoded" `
-Headers @{"Accept" = "application/pdf"} `
-InFile label.zpl `
-OutFile label.pdf

4.5. Java Example

A Java example that uses the Java 11 HttpClient API to send a POST request to convert a ZPL string to a PDF file:

var zpl = "^xa^cfa,50^fo100,100^fdHello World^fs^xz";

// adjust print density (8dpmm), label width (4 inches), label height (6 inches), and label index (0) as necessary
var uri = URI.create("http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/");
var request = HttpRequest.newBuilder(uri)
    .header("Accept", "application/pdf") // omit this line to get PNG images back
    .POST(BodyPublishers.ofString(zpl))
    .build();
var client = HttpClient.newHttpClient();
var response = client.send(request, BodyHandlers.ofByteArray());
var body = response.body();

if (response.statusCode() == 200) {
    var file = new File("label.pdf"); // change file name for PNG images
    Files.write(file.toPath(), body);
} else {
    var errorMessage = new String(body, StandardCharsets.UTF_8);
    System.out.println(errorMessage);
}

4.6. Python Example

A Python example that uses the Requests library to send a POST request to convert a ZPL string to a PDF file:

import requests
import shutil

zpl = '^xa^cfa,50^fo100,100^fdHello World^fs^xz'

# adjust print density (8dpmm), label width (4 inches), label height (6 inches), and label index (0) as necessary
url = 'http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/'
files = {'file' : zpl}
headers = {'Accept' : 'application/pdf'} # omit this line to get PNG images back
response = requests.post(url, headers = headers, files = files, stream = True)

if response.status_code == 200:
    response.raw.decode_content = True
    with open('label.pdf', 'wb') as out_file: # change file name for PNG images
        shutil.copyfileobj(response.raw, out_file)
else:
    print('Error: ' + response.text)

4.7. Ruby Example

A Ruby example that uses a POST request to convert a ZPL string to a PDF file (courtesy Robert Coleman):

require 'net/http'

zpl = '^xa^cfa,50^fo100,100^fdHello World^fs^xz'

# adjust print density (8dpmm), label width (4 inches), label height (6 inches), and label index (0) as necessary
uri = URI 'http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/'
http = Net::HTTP.new uri.host, uri.port
request = Net::HTTP::Post.new uri.request_uri
request.body = zpl
request['Accept'] = 'application/pdf' # omit this line to get PNG images back
response = http.request request

case response
when Net::HTTPSuccess then
    File.open 'label.pdf', 'wb' do |f| # change file name for PNG images
        f.write response.body
    end
else
    puts "Error: #{response.body}"
end

4.8. Node.js Example

A Node.js example that uses a POST request to convert a ZPL string to a PDF file (courtesy Katy LaVallee):

var fs = require('fs');
var request = require('request');

var zpl = "^xa^cfa,50^fo100,100^fdHello World^fs^xz";

var options = {
    encoding: null,
    formData: { file: zpl },
    // omit this line to get PNG images back
    headers: { 'Accept': 'application/pdf' },
    // adjust print density (8dpmm), label width (4 inches), label height (6 inches), and label index (0) as necessary
    url: 'http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/'
};

request.post(options, function(err, resp, body) {
    if (err) {
        return console.log(err);
    }
    var filename = 'label.pdf'; // change file name for PNG images
    fs.writeFile(filename, body, function(err) {
        if (err) {
            console.log(err);
        }
    });
});

4.9. D Language Example

A D language example that uses a POST request to convert a ZPL string to a PDF file (courtesy Andrea Freschi):

import std.stdio;
import std.net.curl;

// adjust print density (8dpmm), label width (4 inches), label height (6 inches), and label index (0) as necessary
auto url = "http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/";
auto zpl = "^xa^cfa,50^fo100,100^fdHello World^fs^xz";

void main() {
    auto conn = HTTP();
    conn.addRequestHeader("Accept", "application/pdf"); // omit this line to get PNG images back
    auto label = url.post!ubyte(zpl, conn);
    if (conn.statusLine.code == 200) {
        label.toFile("label.pdf"); // change file name for PNG images
    } else {
        writeln(conn.statusLine.toString());
    }
}

4.10. C# Example

A C# example that uses a POST request to convert a ZPL string to a PDF file:

byte[] zpl = Encoding.UTF8.GetBytes("^xa^cfa,50^fo100,100^fdHello World^fs^xz");

// adjust print density (8dpmm), label width (4 inches), label height (6 inches), and label index (0) as necessary
var request = (HttpWebRequest) WebRequest.Create("http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/");
request.Method = "POST";
request.Accept = "application/pdf"; // omit this line to get PNG images back
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = zpl.Length;

var requestStream = request.GetRequestStream();
requestStream.Write(zpl, 0, zpl.Length);
requestStream.Close();

try {
    var response = (HttpWebResponse) request.GetResponse();
    var responseStream = response.GetResponseStream();
    var fileStream = File.Create("label.pdf"); // change file name for PNG images
    responseStream.CopyTo(fileStream);
    responseStream.Close();
    fileStream.Close();
} catch (WebException e) {
    Console.WriteLine("Error: {0}", e.Status);
}

4.11. VB.NET Example

A VB.NET example that uses a POST request to convert a ZPL string to a PDF file:

Dim zpl() As Byte = Encoding.UTF8.GetBytes("^xa^cfa,50^fo100,100^fdHello World^fs^xz")

' adjust print density (8dpmm), label width (4 inches), label height (6 inches), and label index (0) as necessary
Dim request As HttpWebRequest = WebRequest.Create("http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/")
request.Method = "POST"
request.Accept = "application/pdf" ' omit this line to get PNG images back
request.ContentType = "application/x-www-form-urlencoded"
request.ContentLength = zpl.Length

Dim requestStream As Stream = request.GetRequestStream()
requestStream.Write(zpl, 0, zpl.Length)
requestStream.Close()

Try
    Dim response As HttpWebResponse = request.GetResponse()
    Dim responseStream As Stream = response.GetResponseStream()
    Dim fileStream As Stream = File.Create("label.pdf") ' change file name for PNG images
    responseStream.CopyTo(fileStream)
    responseStream.Close()
    fileStream.Close()
Catch e As WebException
    Console.WriteLine("Error: {0}", e.Status)
End Try

4.12. ColdFusion Example

A ColdFusion example that uses a POST request to convert a ZPL string to a PDF file:

<cfoutput>
    <cfset zpl="^xa^cfa,50^fo100,100^fdHello World^fs^xz">
    <!-- change type to "image/png" to get PNG images -->
    <cfset type="application/pdf">
    <!-- adjust print density (8dpmm), label width (4 inches), label height (6 inches), and label index (0) as necessary -->
    <cfhttp url="http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/" method="post" result="result">
        <cfhttpparam type="header" name="Content-Type" value="application/x-www-form-urlencoded">
        <cfhttpparam type="header" name="Accept" value="#type#">
        <cfhttpparam type="body" value="#zpl#">
    </cfhttp>
    <cfcontent variable="#result.Filecontent#" type="#type#" reset="true" />
</cfoutput>

4.13. PHP Example

A PHP example that uses a POST request to convert a ZPL string to a PDF file:

<?php

$zpl = "^xa^cfa,50^fo100,100^fdHello World^fs^xz";

$curl = curl_init();
// adjust print density (8dpmm), label width (4 inches), label height (6 inches), and label index (0) as necessary
curl_setopt($curl, CURLOPT_URL, "http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/");
curl_setopt($curl, CURLOPT_POST, TRUE);
curl_setopt($curl, CURLOPT_POSTFIELDS, $zpl);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_HTTPHEADER, array("Accept: application/pdf")); // omit this line to get PNG images back
$result = curl_exec($curl);

if (curl_getinfo($curl, CURLINFO_HTTP_CODE) == 200) {
    $file = fopen("label.pdf", "w"); // change file name for PNG images
    fwrite($file, $result);
    fclose($file);
} else {
    print_r("Error: $result");
}

curl_close($curl);

?>

4.14. Rust Example

A Rust example that uses Reqwest to send a POST request to convert a ZPL string to a PDF file:

use reqwest::Client;
use std::io::Cursor;
use std::fs::File;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    
    let zpl = "^xa^cfa,50^fo100,100^fdHello World^fs^xz";
    
    // adjust print density (8dpmm), label width (4 inches), label height (6 inches), and label index (0) as necessary
    let url = "http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/";
    let response = Client::new()
        .post(url)
        .body(zpl)
        .header("Accept", "application/pdf") // omit this line to get PNG images back
        .send().await?;
    
    if response.status().is_success() {
        let mut file = File::create("label.pdf")?; // change file name for PNG images
        let mut content = Cursor::new(response.bytes().await?);
        std::io::copy(&mut content, &mut file)?;
    } else {
        let error_message = response.text().await?;
        eprintln!("{}", error_message);
    }
    
    Ok(())
}

4.15. Go Language Example

A Go language example that uses a POST request to convert a ZPL string to a PDF file:

package main

import (
    "os"
    "io"
    "io/ioutil"
    "log"
    "bytes"
    "net/http"
)

func main() {

    zpl := []byte("^xa^cfa,50^fo100,100^fdHello World^fs^xz")
    // adjust print density (8dpmm), label width (4 inches), label height (6 inches), and label index (0) as necessary
    req, err := http.NewRequest("POST", "http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/", bytes.NewBuffer(zpl))
    if err != nil {
        log.Fatalln(err)
    }
    req.Header.Set("Accept", "application/pdf") // omit this line to get PNG images back

    client := &http.Client{}
    response, err := client.Do(req)
    if err != nil {
        log.Fatalln(err)
    }
    defer response.Body.Close()

    if response.StatusCode == http.StatusOK {
        file, err := os.Create("label.pdf") // change file name for PNG images
        if err != nil {
            log.Fatalln(err)
        }
        defer file.Close()
        io.Copy(file, response.Body)
    } else {
        body, err := ioutil.ReadAll(response.Body)
        if err != nil {
            log.Fatalln(err)
        }
        log.Fatalln(string(body))
    }
}

4.16. Excel VBA Example

An Excel VBA example that converts all ZPL templates in column A into PNG label images, and adds the images to column B:

Sub Convert()

    'adjust print density (8dpmm), label width (4 inches), label height (6 inches), and label index (0) as necessary
    u = "http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/"
    tempFile = Environ("Temp") & "\excel-label-temp.png"
    Set ws = ActiveWorkbook.Sheets("Sheet1")
    Set http = CreateObject("MSXML2.ServerXMLHTTP")
    Set stream = CreateObject("ADODB.Stream")
    stream.Type = 1 'adTypeBinary

    For Each rw In ws.UsedRange.Rows
        zpl = ws.Cells(rw.Row, 1).Value
        http.Open "POST", u, False
        http.SetRequestHeader "Content-Type", "application/x-www-form-urlencoded"
        http.Send zpl
        If http.Status = 200 Then
            stream.Open
            stream.Write http.responseBody
            stream.SaveToFile tempFile, 2 'adSaveCreateOverWrite
            stream.Close
            rw.RowHeight = 200
            Set pic = ws.Shapes.AddPicture(tempFile, False, True, 1, 1, 1, 1)
            pic.Left = ws.Cells(rw.Row, 2).Left
            pic.Top = ws.Cells(rw.Row, 2).Top
            pic.Height = ws.Cells(rw.Row, 2).Height
        Else
            ws.Cells(rw.Row, 2).Value = http.ResponseText
        End If
    Next rw

End Sub

5. Advanced

Some advanced features provided by the Labelary API:

5.1. Rotate labels

Your ZPL template already defines a label rotation / orientation. Sometimes, however, the label orientation that is most suitable for your printer is not the orientation that you'd like to use during format conversion.

If this is the case, you can ask Labelary to rotate the label image by adding a X-Rotation HTTP header to the request. The value of this header should be the number of degrees to rotate the label clockwise, and may be one of 0, 90, 180 or 270.

5.2. PDF with multiple labels

When requesting a PNG image, you can only get one label back at a time, even if your ZPL defines multiple labels. However, PDF files can contain more than one label. If you are requesting a PDF file and you would like it to contain all of the labels defined in your ZPL template, simply omit the index parameter from the request URL.

For example, instead of sending your request to this URL, which returns the label at index 0 (the first label):

http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/

You can omit the index and instead send your request to this URL:

http://api.labelary.com/v1/printers/8dpmm/labels/4x6/

Note that this option is only available for PDF requests.

5.3. PDF page size and orientation

By default, Labelary generates PDFs whose page size matches the label size. However, you may want to generate PDFs with a different page size. If this is the case, you can add the X-Page-Size and X-Page-Orientation HTTP headers to your request.

The X-Page-Size HTTP header tells Labelary to use a specific PDF page size. Valid values are Letter, Legal, A4, A5 and A6.

The X-Page-Orientation HTTP header tells Labelary to use a specific orientation of the specified page size. Valid values are Portrait and Landscape.

Note that these headers can only be used with PDF requests.

5.4. PDF page layout

If you customize the PDF page size, you may also want to include multiple labels on each page. If this is the case, you can add the X-Page-Layout, X-Page-Align and X-Page-Vertical-Align HTTP headers to your request.

The X-Page-Layout HTTP header tells Labelary to lay out the labels on the PDF pages in a tabular format. The header value must be in the form <columns>x<rows>. For example, a value of 2x3 would generate a PDF file with 6 labels per page, arranged in 2 columns and 3 rows.

The X-Page-Align HTTP header tells Labelary how to align the labels horizontally. Valid values are Left, Right, Center and Justify. The default value is Justify, which distributes extra horizontal whitespace evenly across the page.

The X-Page-Vertical-Align HTTP header tells Labelary how to align the labels vertically. Valid values are Top, Bottom, Center and Justify. The default value is Justify, which distributes extra vertical whitespace evenly across the page.

Note that these headers can only be used with PDF requests.

5.5. How many labels?

Sometimes your ZPL code may define multiple labels. If you want to know how many labels were generated by your ZPL, you can read the value of the X-Total-Count HTTP response header returned by Labelary. The value of this header will be accurate regardless of whether you requested that a single label be rendered (as in a PNG conversion) or you requested that all labels be rendered (as in some PDF conversions).

5.6. Print quality

By default, Labelary generates 8-bit grayscale PNG images optimized for electronic viewing. However, you can add the X-Quality HTTP header to your request in order to customize the quality of the output images. Valid values are Grayscale and Bitonal.

Grayscale generates 8-bit grayscale images suitable for electronic viewing and printing on high-density printers. This is the default setting.

Bitonal generates 1-bit monochrome images suitable for printing on low-density printers. It also generates smaller files (useful when file size is more important than output quality).

This header is only available for PNG requests.

5.7. Linting

EXPERIMENTAL: This option is experimental and may be removed or significantly changed based on feedback.

It is possible to ask Labelary to check your ZPL for potential errors while rendering the ZPL. This process is known as linting. You can enable the Labelary linter by adding the X-Linter HTTP header to your request. Valid values are On (to enable the linter), and Off (to disable the linter). The linter is disabled by default.

Once the linter is enabled, warnings will be returned in the X-Warnings HTTP response header. The maximum number of warnings that will be returned is 20. Additional warnings (if any) will not be returned. Warnings will be returned in pipe-delimited format, and each warning will include 5 attributes:

  1. The byte index of the ZPL section which triggered the warning (integer, never empty)
  2. The byte size of the ZPL section which triggered the warning (integer, never empty)
  3. The name of the ZPL command which triggered the warning (string, may be empty if the warning is not associated with a specific command)
  4. The parameter number of the command parameter which triggered the warning (integer, may be empty if the warning is not associated with a specific parameter)
  5. The warning message (string, never empty)

As an example, the following HTTP response header indicates that the submitted ZPL generated 2 warnings:

X-Warnings: 303|1|^GB|2|Value 1 is less than minimum value 3; used 3 instead|591|3|||Ignored unrecognized content

The first warning, starting at byte index 303 and extending for 1 byte, indicates that the second parameter of the ^GB command used a value that was too small. The second warning, starting at byte index 591 and extending for 3 bytes, indicates that some unrecognized content was found between ZPL commands. The command name and parameter number for the second warning are empty, since the warning was not generated inside any specific ZPL command.

5.8. Data Extraction

EXPERIMENTAL: This option is experimental and may be removed or significantly changed based on feedback.

In some cases you may wish to extract data from your ZPL for further processing. In these scenarios it is possible to ask Labelary to provide a list of output labels and data fields in JSON format by simply sending an Accept: application/json HTTP request header.

The resultant JSON will contain all text displayed on the labels. It will not contain information about lines, rectangles, circles, or other graphics. The output JSON will contain the human-readable text for any barcodes, but will not contain the actual data encoded in the barcode symbols. Barcodes without human-readable text above or below the barcode will therefore not be included in the generated JSON. The order of the data fields in the output JSON will always match the field definition order in the ZPL.

Specific data fields can be found based on:

  • Field order, e.g. response.labels[0].fields[3].data
  • Physical position, e.g. response.labels[0].fields.find(f => f.x == 50 && f.y == 150).data
  • Pattern matching, e.g. response.labels[0].fields.find(f => /^[0-9]{5}$/.test(f.data)).data

Below is an example input ZPL template which defines two labels, each of which contains two data fields, and the corresponding output JSON:

^XA
^FT50,50^A0,50^FDField 1^FS
^FT50,150^A0,50^FDField 2^FS
^XZ
^XA
^FT50,50^A0,50^FDField 3^FS
^FT50,150^A0,50^FDField 4^FS
^XZ

{
    "labels": [
        { "fields": [ { "x": 50, "y": 50, "data": "Field 1" }, { "x": 50, "y": 150, "data": "Field 2" } ] },
        { "fields": [ { "x": 50, "y": 50, "data": "Field 3" }, { "x": 50, "y": 150, "data": "Field 4" } ] }
    ]
}