> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inkrelay.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Raw printing (ESC/POS, ZPL)

> Send printer language bytes straight to receipt and label printers.

Raw jobs skip rendering and send your bytes to the printer unchanged, which is how receipt printers (ESC/POS) and label printers (ZPL) are usually driven. Use `raw_base64` or `raw_uri`; options such as `paper` and `duplex` do not apply.

```bash theme={null}
# ZPL label: 4x6 with one line of text
CONTENT=$(printf '^XA^FO50,50^A0N,50,50^FDInkRelay^FS^XZ' | base64)
curl https://api.inkrelay.app/v1/printjobs -H "Authorization: Bearer $INKRELAY_API_KEY" -H "Content-Type: application/json" \
  -d "{\"printerId\":\"PRINTER_ID\",\"title\":\"Label\",\"contentType\":\"raw_base64\",\"content\":\"$CONTENT\"}"
```

```python theme={null}
import base64
import os

from inkrelay import InkRelay

ir = InkRelay(api_key=os.environ["INKRELAY_API_KEY"])
printer_id = "PRINTER_ID"

# ESC/POS receipt: init, text, feed and cut
data = b"\x1b@" + b"InkRelay receipt\n" + b"\n\n\n" + b"\x1dV\x00"
ir.print_jobs.create({"printerId": printer_id, "title": "Receipt", "contentType": "raw_base64", "content": base64.b64encode(data).decode()})
```

With `raw_uri`, `content` is a URL that serves the bytes. The same rules as `pdf_uri` apply: 25 MB at most, no redirects, no private addresses, ports 80 and 443 only, and optional `authentication` for Basic protected URLs. `qty` prints the raw job that many times.

`raw_base64` content must be canonical base64 text. Unlike PDF jobs, InkRelay does not inspect the bytes beyond their size, so a malformed label prints whatever the printer makes of it.

## Choosing the right printer

The printer's driver must accept raw data.

* **Windows**: the agent writes your bytes with the `RAW` datatype, which bypasses the driver's rendering. Use the printer's own driver or the "Generic / Text Only" driver. If the printer is shared over the network, add it as a local TCP/IP printer first (see [Install the agent](/install-agent)).
* **macOS and Linux**: the agent submits the job to CUPS with the `application/vnd.cups-raw` format. Create the queue with the raw driver (`lpadmin -p NAME -E -v DEVICE_URI -m raw`, or "Raw Queue" in older CUPS) so CUPS does not try to filter the data.

Check that the printer is online and marked live before you send the first job.

## Test printers

Test mode includes two raw printers, so you can build and test without hardware:

* **Test Receipt 80mm** accepts ESC/POS bytes.
* **Test Label 4x6** accepts ZPL bytes.

Both finish in about 3 seconds like the other test printers. See [Test mode](/guides/test-mode).

## Encoding pitfalls

* Send bytes, not text. Build the payload as bytes (`bytes` in Python, `Buffer` in Node) and base64 encode that, so the characters your printer needs are not changed on the way.
* Text inside a receipt uses the printer's code page. If you need accents or non-Latin scripts, select the code page with the matching ESC/POS command (`ESC t n`) and encode the text to that code page yourself, for example `"é".encode("cp858")`. Do not send UTF-8 unless the printer is configured for it.
* Include the line feeds and the cut command your printer needs. Receipt printers print nothing until they receive a line feed, and they do not cut by themselves.
* ZPL commands end with `^XZ`. A label that is missing it waits for more data.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.