Receipt Printer API: the Complete Guide

Every way software prints receipts in 2026 — ESC/POS, port 9100, vendor SDKs, cloud relays — and how a local HTTP API on the LAN compares.

Published 2026-07-24 · For developers

There is no single "receipt printer API." What exists is a stack of seven approaches — OS drivers, raw TCP on port 9100, direct ESC/POS over USB or serial, vendor SDKs like Epson ePOS and Star CloudPRNT, cloud relays like PrintNode, local agents like QZ Tray, and LAN HTTP APIs — each with different latency, hardware lock-in, and failure modes. This guide covers all of them with working code, so you can pick the right one before you write a line of integration.

The seven ways software prints receipts

Every receipt that has ever printed traveled one of these paths:

#ApproachTransportStatus feedbackWorks offlineHardware lock-in
1OS driver / OPOS / JavaPOSOS print spoolerDriver-dependentYesPer-model drivers
2Raw TCP port 9100LAN socketDLE EOT (if you implement it)YesNone — any ESC/POS printer
3Direct ESC/POS over USB/serial/BTLocal busDLE EOT / ASBYesNone, but per-OS transport code
4Epson ePOSHTTP to the printerRich, built inYes (LAN)Epson intelligent printers only
5Star CloudPRNTPrinter polls your serverReported on pollNo — needs your server reachableStar printers only
6Cloud relay (PrintNode, BizPrint)Internet round-tripJob-levelNoNone, but needs a PC agent on site
7LAN HTTP API (print server / node)HTTP on the LANDepends on implementationYesNone

The rest of this guide walks each row: what it is, code where code is useful, and where it breaks.

1. OS drivers, OPOS, and JavaPOS

The oldest path: install a Windows driver (or CUPS on Linux/macOS), and the printer appears as a system printer. OPOS and JavaPOS are 1990s-era standards that wrap the driver in a POS-flavored object model — Printer.PrintNormal(), CashDrawer.OpenDrawer().

It still works, and for classic fat-client Windows POS software it is often the path of least resistance. The costs show up over time: a driver per printer model per OS version, spooler stalls that present as "printer offline" tickets, and no story at all for web apps, mobile apps, or Linux kiosks. Modern POS development has largely moved off this path, which is why the rest of this list exists.

2. Raw TCP on port 9100

Almost every networked receipt printer listens on TCP port 9100 and prints whatever bytes arrive. No handshake, no header, no acknowledgment — you open a socket, write ESC/POS, and close it. This is the same "JetDirect" convention office printers use, and it is the closest thing POS printing has to a universal API.

// Node.js — print a receipt to any network ESC/POS printer, zero dependencies
import net from "node:net";
 
const receipt = Buffer.concat([
  Buffer.from([0x1b, 0x40]),            // ESC @  — initialize
  Buffer.from("COFFEE CORNER\n"),
  Buffer.from("1x Flat white     4.50\n"),
  Buffer.from([0x1b, 0x64, 0x05]),      // ESC d 5 — feed 5 lines
  Buffer.from([0x1d, 0x56, 0x00]),      // GS V 0 — full cut
]);
 
const socket = net.createConnection(9100, "192.168.1.87", () => {
  socket.end(receipt);
});

That is a complete, working integration. It is also the whole problem: the socket accepts your bytes whether or not the printer has paper, has its cover open, or is on fire. Getting real feedback means interleaving DLE EOT status queries into the stream and parsing single-byte replies — see our guides to port 9100 printing and the DLE EOT status commands for the full picture.

3. Direct ESC/POS over USB, serial, or Bluetooth

Same bytes, different pipe. For a USB or serial printer you talk to the device node directly, usually through a library that hides the transport. In Python, python-escpos is the mature option and maintains the community's database of per-printer quirks:

from escpos.printer import Usb
 
# VID/PID from lsusb; profile picks the right code page + feature set
p = Usb(0x04b8, 0x0e28, profile="TM-T88III")
p.text("COFFEE CORNER\n")
p.barcode("4006381333931", "EAN13")
p.cut()

The catch is that "ESC/POS" is a standard every manufacturer forks. Code that renders perfectly on an Epson garbles on a $60 generic — wrong code page, unsupported cut variant, different image commands. That fragmentation is documented enough that python-escpos needed an entire capabilities database (escpos-printer-db) to cope. Our ESC/POS command reference covers the commands and the quirks in byte-level detail.

You also inherit the transport: USB permissions on Linux, driver interference on Windows, Bluetooth pairing flows on Android. Each one is per-OS code you now own.

4. Vendor SDKs: Epson ePOS, StarPRNT, Star CloudPRNT

The printer vendors solved fragmentation by selling you a nicer API that only works on their hardware.

Epson ePOS — Epson's "intelligent" TM printers run an embedded web service. Your app POSTs ePOS-Print XML (or uses the JS/mobile SDK) over HTTP straight to the printer. No drivers, works from a browser on the same LAN, rich status built in. The SDK is free; the monetization is the hardware: a TM-m30III runs $275–541 and an OmniLink TM-T88VII $370–571, versus $40–160 for a generic ESC/POS printer.

StarPRNT — Star's equivalent command language plus SDKs for iOS, Android, and desktop. Same shape: good tooling, Star hardware only.

Star CloudPRNT — inverts the flow. The printer polls a URL on your server every few seconds asking "anything to print?" and pulls the job down. Brilliant for online ordering (no inbound firewall holes at the restaurant), but you must build and host a compliant server, and every ticket eats the polling delay.

The full architectural trade-offs — push vs poll, latency, status — are in ESC/POS vs ePOS vs CloudPRNT vs StarPRNT.

5. Cloud print relays: PrintNode and friends

A cloud relay gives you a true REST API: POST a job to their servers, and a desktop client running on a PC at the venue pulls it down and prints via local drivers.

curl -u "$PRINTNODE_API_KEY:" https://api.printnode.com/printjobs \
  -H "Content-Type: application/json" \
  -d '{ "printerId": 4212, "contentType": "raw_base64", "content": "G0BIZWxsbwodVgA=" }'

PrintNode is the category leader: free for 50 prints/month, then $9/$29/$99 monthly tiers by volume, with integrator plans at $60–$500/month. It is genuinely convenient for e-commerce order printing across many sites you do not control.

The two structural costs: a full PC, Mac, or Pi must run the client at every site, and every print is an internet round-trip. When the venue's uplink drops — or the relay has a bad day — printing stops. For kitchen tickets during a dinner rush, that is disqualifying. The October 2025 AWS us-east-1 outage took cloud-dependent restaurant printing down for about 15 hours.

6. Local agents: QZ Tray

QZ Tray attacks a narrower problem: browsers cannot open sockets, so how does a web-based POS print? Answer: install a Java agent on each workstation; your web page talks to it over a local websocket, and it forwards raw bytes or PDFs to any local or network printer.

It works and it is widely deployed. The costs are the per-workstation install (and keeping the Java app running), plus certificate friction: silent printing requires a signing certificate, which is how QZ monetizes — $599/yr Premium, $2,999/yr white-label. That price point is useful market data: ISVs demonstrably pay real money to make browser printing work. Our guide to printing from a web app compares this path against WebUSB and LAN alternatives.

7. A LAN HTTP API

The gap in the list so far: nothing gives you a modern JSON API that is local. Cloud relays have the API but not the locality; port 9100 has the locality but no API, no discovery, and no honest status unless you implement a binary protocol yourself.

The missing shape is a small HTTP server sitting next to the printer, on the LAN. This is what we build at ProxyNodes: a ~$10 ESP32-based node that plugs into the printer and serves one HTTP/JSON API. Any app on the network — web, mobile, backend — prints with a plain HTTP call:

curl http://proxynode-a1b2.local/print \
  -H "Content-Type: application/json" \
  -d '{
    "type": "print",
    "id": "job-42",
    "endpoint": "receipt",
    "lines": [
      { "type": "text", "value": "COFFEE CORNER", "align": "center", "bold": true },
      { "type": "text", "value": "1x Flat white     4.50", "align": "left" },
      { "type": "feed", "lines": 2 },
      { "type": "cut" }
    ]
  }'

Status is a GET /status away, and it is honest — when the node genuinely cannot take a reading (say, a write-only printer that ignores status queries), it flags the reading as unknown (known: false) instead of reporting a confident guess. A healthy node answers:

{
  "online": true,
  "printers": [
    { "endpoint": "receipt", "online": true, "paperOut": false, "coverOpen": false, "drawerOpen": false }
  ]
}

Nodes advertise over mDNS (_proxynode._tcp), push state changes over an SSE /events stream, and — because legacy systems should not need any integration — also accept raw ESC/POS on port 9100 like any network printer, DLE EOT status replies included. Nothing in the print path touches the internet.

Where ProxyNodes is today

ProxyNodes is at the prototype stage: the ESP32-S3 firmware drives a real thermal printer end-to-end (print, cut, live paper/cover/drawer status), and a byte-compatible digital twin lets you run the same API with no hardware at all. The protocol, firmware, and simulator will be source-open at launch. Vendor-protocol emulation (ePOS XML, CloudPRNT) is on the roadmap, not shipped.

You can try the API today without any hardware — the digital twin is a LAN service your app cannot tell from a real node, with fault injection for paper-out, cover-open, and offline states.

Choosing an approach

A compressed decision guide:

  • Legacy Windows POS, printers already installed — stay on drivers/OPOS until something forces the move.
  • You control the app and the printer is networked — raw port 9100 is the simplest thing that works; budget for status handling.
  • All-Epson fleet, budget approved — ePOS is genuinely pleasant; you are paying $200–450 per lane for it.
  • Online ordering into restaurants you don't control, Star hardware — CloudPRNT was built for exactly this.
  • Printing across many remote sites, latency-tolerant — a cloud relay like PrintNode earns its fee.
  • Web POS, workstations you manage — QZ Tray today; WebUSB where the browser matrix allows.
  • You want one JSON API, any printer, and printing that survives an internet outage — that is the LAN-node shape, and it is what we are building. Start with the simulator.

More depth on the developer stack lives at the developers hub.

Frequently asked questions

Is there a standard REST API for receipt printers?
No. The closest things are vendor-specific: Epson ePOS accepts XML over HTTP on the printer itself, and cloud relays like PrintNode expose REST endpoints that route through an on-site agent. There is no vendor-neutral standard, which is why most integrations still speak raw ESC/POS.
What is the simplest way to print to a network receipt printer from code?
Open a TCP socket to the printer's IP on port 9100 and write ESC/POS bytes. It works in any language with sockets and requires no drivers. The trade-off is zero feedback: you must implement DLE EOT status queries yourself to know whether the print physically happened.
Can I print receipts directly from a browser?
Not to a socket — browsers block raw TCP. Working options are a local agent like QZ Tray, WebUSB or Web Serial for directly attached printers on Chromium browsers, a cloud relay, or an HTTP endpoint on the LAN that the page can call, such as an intelligent printer or a print node.
Do cloud print relays work when the internet is down?
No. A cloud relay routes every job through its servers, so a dropped uplink or a provider outage stops printing entirely. The AWS outage of October 2025 took cloud-dependent restaurant printing down for roughly 15 hours. Anything that must print during service should have a LAN-only path.
Why not just use the printer manufacturer's SDK?
Vendor SDKs are free and well documented, but they only drive that vendor's hardware, which costs $275 and up per printer versus $40 to 160 for generics. Building on one also means your software inherits the whitelist problem: every new hardware model is an engineering project.

Related reading