PDF printing
Print PDFs from a web app to a local printer.
Your software already makes PDFs: packing slips, delivery notes, invoices, courier labels. Printing them without a person clicking a print dialog is the hard part. Send the PDF to Printr and it comes out of the right printer, silently, with a record of what happened.
How it works
The PDF travels to the Printr agent on a Windows or Mac computer at the site, and that computer's own printer driver prints it. That is why one API reaches such different printers: an office laser printing an A4 invoice, a Zebra printing a courier's PDF label, and a Dymo LabelWriter printing an address label, each through the driver its manufacturer wrote.
So the printer has to be set up on that computer, the way you would to print to it
from any other program. A printer addressed directly over the network has no driver
behind it, and a PDF sent to one is refused with 409 rather than coming
out as pages of gibberish.
Send a PDF
JSON cannot carry a binary file, so the PDF goes base64-encoded in the
pdf field. The encoded string can be up to 8,000,000 characters, which is
about 6 MB of PDF. Anything that is not a PDF is refused before it reaches a printer.
curl -X POST https://api.getprintr.co.uk/v1/printjobs \
-H "Authorization: Bearer $PRINTR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1041-slip" \
-d "{\"printer_id\": 12, \"pdf\": \"$(base64 < packing-slip.pdf | tr -d '\n')\"}"
import { readFile } from 'node:fs/promises';
const pdf = await readFile('packing-slip.pdf');
const res = await fetch('https://api.getprintr.co.uk/v1/printjobs', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PRINTR_TOKEN}`,
'Content-Type': 'application/json',
'Idempotency-Key': `order-${order.id}-slip`,
},
body: JSON.stringify({ printer_id: 12, pdf: pdf.toString('base64'), copies: 2 }),
});
import base64
import os
import requests
with open("packing-slip.pdf", "rb") as f:
pdf = base64.b64encode(f.read()).decode()
requests.post(
"https://api.getprintr.co.uk/v1/printjobs",
headers={
"Authorization": f"Bearer {os.environ['PRINTR_TOKEN']}",
"Idempotency-Key": f"order-{order.id}-slip",
},
json={"printer_id": 12, "pdf": pdf, "copies": 2},
timeout=30,
).raise_for_status()
$job = Http::withToken(config('services.printr.token'))
->withHeaders(['Idempotency-Key' => "order-{$order->id}-slip"])
->post('https://api.getprintr.co.uk/v1/printjobs', [
'printer_id' => 12,
'pdf' => base64_encode(Storage::get("slips/{$order->id}.pdf")),
'copies' => 2,
])
->throw()
->json('data');
copies goes from 1 to 100 and is passed to the printer, rather than
the file being repeated: two PDFs joined end to end make one broken file, not two
copies. A document counts as one label on your plan, however many pages it has.
Printing to a Dymo
A Dymo LabelWriter 450, 550 or 5XL takes the same request. Dymo's own driver turns the PDF into a label, so the one thing to get right is the page size: make the PDF the size of the label you have loaded, not A4, and it prints at the size you designed it.
Tips from real printers
- Embed your fonts when you generate the PDF. The computer printing it may not have them installed.
- Match the page size to the paper. Most drivers shrink an A4 page to fit a label, and put a label-sized page in the corner of a sheet of A4.
- Use an idempotency key per document, such as the order number plus the kind of document, so a retry after a timeout never prints a second invoice.
- Poll for the result the same way as any other job:
printed,failed,cancelledandexpiredare final. See the cloud printing guide for an example.
Labels as well as documents
The same API prints thermal labels from templates or raw ZPL, so the shipping label and the packing slip for an order can come from the same code. See printing ZPL to a Zebra from a web app.
Try it against your own printers
The free plan is 100 labels a month on one computer, with the full API. No card required. Once you are signed in, the reference fills in your own printer ids.