Print jobs and documents
A job is one thing to print, sent to one printer, with the options that go with it. Content arrives inline as a URL or as base64, or as a document you uploaded once and can print many times.
Two behaviours here catch people out, so they have sections of their own rather than a footnote. Capability mismatches never reject a job: ask for duplex on a printer that does not report duplex support and the job is accepted with a warning attached, because capability reporting is best effort and refusing to print would be worse than printing wrong. And some drivers cannot confirm that a page physically came out, so a successful job carries completion_confidence telling you whether the printer said so or only the spooler did.
Documents
Jobs can carry their content inline, but a document can be uploaded once and printed many times: one packing-slip PDF sent to three printers, for example, instead of re-sending or re-hosting the same bytes.
| Method | Path | Notes |
|---|---|---|
| POST | /documents | Either a direct upload (Content-Type: application/pdf, body up to 50 MB) or a JSON body with source_url and optional fetch_auth for a server-side fetch |
| GET | /documents/{id} | |
| DELETE | /documents/{id} |
Documents expire after 24 h by default; override with expire_after_seconds.
Jobs
| Method | Path | Notes |
|---|---|---|
| POST | /jobs | Create (see below). Returns 201 with the full job object. |
| GET | /jobs | Filters: printer_id, device_id, status, created_after/created_before, metadata[...] |
| GET | /jobs/{id} | |
| POST | /jobs/{id}/cancel | Cancels if the job is not yet in a terminal state. Returns 409 job_not_cancelable if it is already printing or finished. |
| GET | /jobs/{id}/events | Ordered state-transition timeline for the job |
Create request. Send Idempotency-Key as a header, not a body field. Everything except printer_id and content is optional:
{
"printer_id": "prn_8f2k1",
"title": "Order #12345 label",
"content": {
"format": "pdf",
"url": "https://example.com/label.pdf",
"fetch_auth": { "type": "basic", "username": "orders", "password": "..." }
},
"copies": 2,
"options": {
"duplex": "long_edge",
"color": false,
"paper": "A4",
"dpi": "300x300",
"page_ranges": "1,3-5",
"tray": "Tray 2",
"media": null,
"collate": true,
"fit_to_page": true,
"rotate": 0,
"pages_per_sheet": 1
},
"queue_if_offline": true,
"expire_after_seconds": 3600,
"metadata": { "order_id": "12345" }
}
| Field | Values | Notes |
|---|---|---|
content.format | pdf, raw | raw is passed to the printer byte for byte: ZPL, ESC/POS, or anything else the printer speaks natively. |
content.url, content.base64, content.document_id | Exactly one. url is fetched by PrintSocket at print time; base64 carries the bytes inline (50 MB limit after decoding); document_id prints a document uploaded earlier. | |
content.fetch_auth | { "type": "basic", "username", "password" } | Only with url. HTTP basic auth sent on the fetch. |
copies | 1 or more, default 1 | Above the printer's copies_max the job is accepted with a warning. See Copies below. |
options.duplex | long_edge, short_edge, none | |
options.color | boolean | |
options.paper | a name from the printer's capabilities.papers | Passed to the driver as its media name, so it has to be one the driver knows. |
options.dpi | a value from capabilities.dpis, such as 300x300 | |
options.page_ranges | 1,3-5 | Pages and ranges, comma separated. PDF only. |
options.tray, options.media | names from capabilities.trays and capabilities.media | Input slot and media type. |
options.collate, options.fit_to_page | boolean | |
options.rotate | 0, 90, 180, 270 | |
options.pages_per_sheet | 1 or more | |
queue_if_offline | boolean, default true | With false, a job to an offline printer is rejected immediately with 422 printer_offline instead of waiting. |
expire_after_seconds | default 86400 | A job still queued after this long goes to expired. |
metadata | up to 20 string values | Filterable on GET /jobs. |
Options are not validated against the printer's capabilities, except for the warnings described below. A printer's default_options fill in anything a job leaves out.
Raw content is the same call with a different format. A ZPL label, base64-encoded:
{
"printer_id": "prn_8f2k1",
"title": "Shipping label 1Z999AA10123456784",
"content": {
"format": "raw",
"base64": "XlhBCl5GTzUwLDUwXkEwTiw0MCw0MF5GRE9yZGVyIDEyMzQ1XkZTCl5YWg=="
}
}
The bytes decode to ^XA ^FO50,50^A0N,40,40^FDOrder 12345^FS ^XZ. Print options do not apply to raw content: the printer language carries its own, and the job is passed through unvalidated. The ZPL and ESC/POS guides cover what each language needs.
Job object:
{
"id": "job_5tq8p",
"object": "job",
"printer_id": "prn_8f2k1",
"device_id": "dev_7hk2m",
"title": "Order #12345 label",
"status": "printing",
"copies": 2,
"options": { "duplex": "long_edge", "paper": "A4" },
"completion_confidence": null,
"warnings": [
{ "code": "option_unsupported", "message": "duplex printing requested but the printer does not report duplex support." }
],
"error": null,
"created_at": "2026-07-29T14:02:11Z",
"queued_at": "2026-07-29T14:02:11Z",
"sent_at": "2026-07-29T14:02:12Z",
"completed_at": null,
"expires_at": "2026-07-30T14:02:11Z",
"metadata": { "order_id": "12345" },
"rendering": null
}
statusis one of the states below, andqueued_at,sent_at, andcompleted_atare set as the job reaches each one.completion_confidenceisnulluntil the job succeeds, thenprinterorspooler(see below).warningsare non-fatal notices; the job proceeds. Most are attached at creation; a job converted for a printer with a page language may gain more when it is sent. The codes are listed under Warnings.errorisnullunless the job failed, then{ "code", "message" }. The agent reportscontent_unavailablewhen it could not fetch the job's content, andspool_errorwhen the local spooler rejected or lost the job; the message carries the operating system's own detail. For printers with a page language set,render_failedmeans the PDF could not be converted (the message says why), andagent_update_requiredmeans the printer's agent is too old to convert.renderingisnullunless the job's PDF was converted for a printer with a page language, then{ "language", "dpi", "pages" }.
Status lifecycle (a single status field, with strict transitions):
created ──► queued ──► sent ──► printing ──► succeeded
│ │ │
├──────────┴──────────┴──────► failed (terminal, error populated; straight from
│ queued when the agent can't start the job)
├────────────────────────────► canceled (terminal, via /cancel)
└────────────────────────────► expired (terminal, expire_after elapsed)
queued: accepted, waiting for the device to come online and pick it upsent: delivered to the agent and handed to the OS spoolerprinting/succeeded: reported wherever the OS driver supports it
Some drivers cannot confirm that a page actually came out. Those jobs go sent → succeeded when the spooler finishes, and the job carries "completion_confidence": "spooler" instead of "printer". If a print must be confirmed at the hardware, check that field rather than status alone.
Copies
copies is a single top-level field. PrintSocket hands it to the platform's own copy mechanism (-n on CUPS, DEVMODE on Windows, repeating the payload for raw content) rather than queuing the job several times. One job in, one job in the print queue.
Warnings
Capability mismatches are never rejected. Requesting an option the printer doesn't report support for doesn't fail the job. Capability reporting is best-effort and agents can't always know. The job is accepted with a warnings entry attached, and webhook payloads carry the warnings too, so you can surface them without blocking prints that would have worked. raw content is likewise passed through to the printer unvalidated.
| Warning code | Meaning |
|---|---|
option_unsupported | An option was requested that the printer's reported capabilities don't cover (e.g. color: true or a duplex mode on a printer reporting neither). The option is passed to the driver anyway. |
copies_exceeds_max | copies is greater than the printer's reported copies_max. Behavior beyond the maximum is driver-defined. |
page_scaled | A converted PDF page did not match the label and was scaled to fit. Barcodes may not scan. |
page_cropped | A converted PDF page did not match the label and, because the job set fit_to_page: false, was printed at full size and cropped. |
paper_unrecognized | A converted job named a paper the converter could not size, so the PDF page's own size was used. |
page_ranges_ignored | A converted job's page_ranges could not be read, so every page was printed. |
The last four only occur for printers with a page language set.