Cloud printing API
Print from a web app to any local printer.
Your software runs in the cloud. Your printers are in a warehouse or an office, behind a router, with no public address. This is how to connect the two, and how to do it with Printr in one HTTP request.
Why printing from the cloud is awkward
A server cannot reach a printer on someone else's network. The printer has a private address, the router in front of it blocks incoming connections, and the people who run that network are right not to open a port for you. The browser's print dialog does not help either: it needs a person to click it, and it cannot print a label silently to a Zebra on a packing bench.
The usual workarounds each have a cost:
- A print server on site, reached over a VPN or an opened port. Someone has to build it, secure it and keep it running.
- A browser extension or local bridge on every workstation. It prints silently, but only from a computer with a person at it and the software installed.
- Google Cloud Print, which many older integrations used, was shut down at the end of 2020.
How Printr does it
- Install the agent on one computer at each site, Windows or Mac. It finds the printers that computer can print to, whether networked or plugged in, and connects out to Printr. Nothing connects in.
- Your software sends a print job to the Printr API: a label template with data, raw ZPL or TSPL2, or a PDF.
- The agent collects the job and prints it, reporting each step back, so you can ask the API what happened.
Every job is tracked through seven states, sending the same job twice with an idempotency key prints it once, and a failed job tells you why and whether a retry is worth it.
Send a print job
Get a token from the dashboard, find a printer id with
GET https://api.getprintr.co.uk/v1/printers, then post a job. Each example sends a label
from a template and reads back the job id. The idempotency key is your own reference
for this job, so a retry after a timeout cannot print a second label.
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-label" \
-d '{
"printer_id": 7,
"template": "shipping-label",
"data": { "order": "SO-1041", "customer": "Acme Ltd" }
}'
const BASE = 'https://api.getprintr.co.uk/v1';
const res = await fetch(`${BASE}/printjobs`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PRINTR_TOKEN}`,
'Content-Type': 'application/json',
'Idempotency-Key': `order-${order.id}-label`,
},
body: JSON.stringify({
printer_id: 7,
template: 'shipping-label',
data: { order: order.reference, customer: order.customer },
}),
});
if (!res.ok) throw new Error(`Printr said ${res.status}`);
const { data: job } = await res.json();
import os
import requests
BASE = "https://api.getprintr.co.uk/v1"
res = requests.post(
f"{BASE}/printjobs",
headers={
"Authorization": f"Bearer {os.environ['PRINTR_TOKEN']}",
"Idempotency-Key": f"order-{order.id}-label",
},
json={
"printer_id": 7,
"template": "shipping-label",
"data": {"order": order.reference, "customer": order.customer},
},
timeout=10,
)
res.raise_for_status()
job = res.json()["data"]
use Illuminate\Support\Facades\Http;
$job = Http::withToken(config('services.printr.token'))
->withHeaders(['Idempotency-Key' => "order-{$order->id}-label"])
->post('https://api.getprintr.co.uk/v1/printjobs', [
'printer_id' => 7,
'template' => 'shipping-label',
'data' => ['order' => $order->reference, 'customer' => $order->customer],
])
->throw()
->json('data');
using System.Net.Http.Headers;
using System.Net.Http.Json;
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("PRINTR_TOKEN"));
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.getprintr.co.uk/v1/printjobs")
{
Content = JsonContent.Create(new
{
printer_id = 7,
template = "shipping-label",
data = new { order = "SO-1041", customer = "Acme Ltd" },
}),
};
request.Headers.Add("Idempotency-Key", "order-1041-label");
var response = await http.SendAsync(request);
response.EnsureSuccessStatusCode();
A new job answers 202 Accepted with its id and the state
queued. Sending the same idempotency key again answers 200
with the original job instead of printing twice.
Find out whether it printed
Poll the job until it reaches one of the four final states:
printed, failed, cancelled or
expired. A failed job carries an error code, a plain explanation and a
retryable flag.
async function waitForJob(id) {
const final = ['printed', 'failed', 'cancelled', 'expired'];
for (;;) {
const res = await fetch(`${BASE}/printjobs/${id}`, {
headers: { Authorization: `Bearer ${process.env.PRINTR_TOKEN}` },
});
const { data: job } = await res.json();
if (final.includes(job.state)) return job;
await new Promise((resolve) => setTimeout(resolve, 2000));
}
}
What you can send
- A template with data. Keep the label layout in Printr, edit and version it in the dashboard, and send only the values. Values are cleaned of printer control characters first.
- Raw ZPL or TSPL2 for Zebra, TSC and Xprinter printers, checked for a complete label before it is sent. See printing ZPL to a Zebra from a web app.
- A PDF, printed by the printer's own driver: packing slips and invoices on an office printer, or labels on a Dymo. See printing PDFs from a web app.
Questions integrators ask
Do I need to open firewall ports or set up a VPN?
No. The agent connects out to Printr over HTTPS, the same way a browser does, and collects work from there. Nothing connects into the building.
What happens if the computer at the site is offline?
Jobs wait for it. Each job has a time to live, 15 minutes by default and adjustable per job with ttl_seconds, after which it expires rather than printing a stale backlog when the computer comes back.
Can I print to USB printers?
Yes. The agent prints to printers on the network and printers plugged into its computer, and both look the same from the API.
How do I find out whether a job printed?
Poll GET /printjobs/{id} until the state is printed, failed, cancelled or expired. Those four are final. There are no webhooks.
Is there an SDK?
No. It is a plain REST API with JSON and bearer tokens, so any HTTP client works. Signed-in customers can also download an OpenAPI document from the dashboard to generate a client.
What are the limits?
600 requests a minute per token, and a monthly print allowance set by your plan. The free plan is 100 labels a month on one computer.
The full list of endpoints, fields, states and errors is in the API reference, also available as Markdown for AI coding assistants.
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.