Docs

A quickstart, then the full v1 API reference — every endpoint, object, and error the API actually ships today.

Quickstart

1. Create an account

Sign up at https://app.printsocket.com. There is no password — you get a sign-in link by email. Name your account and you land on the dashboard.

2. Get an API key

In Settings → API keys, create a key. Pick test mode to start: test keys get a virtual device and printer that simulates the whole job lifecycle, so you can build before any hardware is involved. The secret is shown once — store it now.

curl https://api.printsocket.com/v1/me \
  -H "Authorization: Bearer sk_test_..."

3. Enroll a machine

When you are ready for real printers, open Devices → Add device. That gives you a single-use enrollment token and the installer links. Install the agent on the machine that talks to your printers, then enroll it:

psagent enroll -token enr_... -server https://api.printsocket.com
psagent run

The agent connects outbound over a WebSocket — nothing needs to be reachable from the internet. It reports every printer queue it can see, with capabilities, and keeps them updated.

4. Find your printer

curl "https://api.printsocket.com/v1/printers?state=online" \
  -H "Authorization: Bearer sk_test_..."

5. Send a job

curl https://api.printsocket.com/v1/jobs \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-12345" \
  -d '{
    "printer_id": "prn_8f2k1",
    "title": "Order #12345 label",
    "content": { "format": "pdf", "url": "https://example.com/label.pdf" },
    "options": { "paper": "A4" }
  }'

Poll GET /v1/jobs/{id}, read the transition history at GET /v1/jobs/{id}/events, or register a webhook and get pushed the state changes as they happen. Everything on this page is also doable from the dashboard, including sending a test print without writing any code.

API reference

PrintSocket API — v1 reference

A lightweight agent runs on your machine, connects outbound to PrintSocket, and exposes that machine's printers (and scales) to this REST API. No inbound ports, no print server to run.

Base URL: https://api.printsocket.com/v1

1. Authentication

Test mode

sk_test_ keys get a virtual device and printer that simulates the full job lifecycle — queued → sent → printing → succeeded. You can build and test an integration end to end before any hardware is enrolled. Test-mode data is completely separate from live data, and test jobs never count toward plan limits.

2. Conventions

Requests and responses

Pagination

All list endpoints are cursor-based, newest first by default.

Request: ?limit=50&cursor=<opaque>&order=asc|desc (limit default 25, max 100)

{
  "data": [ ... ],
  "has_more": true,
  "next_cursor": "eyJpZCI6..."
}

Pass next_cursor back as cursor to fetch the next page. Stop when has_more is false.

Errors

One shape everywhere:

{
  "error": {
    "type": "invalid_request | authentication | permission | not_found | conflict | rate_limited | billing | server",
    "code": "printer_offline",
    "message": "Printer prn_8f2k1 is offline; the job was not accepted because queue_if_offline=false.",
    "param": "printer_id",
    "request_id": "req_9x4..."
  }
}

Branch on type for broad handling and code for specific cases. message is human-readable and may change; param names the offending field on validation errors.

Status codes: 200/201, 400 (validation), 401 (auth), 402 (plan limit, type billing), 403 (scope), 404, 409 (state conflict), 413 (payload over 50 MB), 422 (semantically invalid, e.g. an option the printer doesn't support), 429 (with Retry-After), 5xx.

Plan limits return 402 with type billing, in live mode only: creating a job past your plan's monthly job cap gives plan_limit_reached (the message names the plan and the reset date), and enrolling past the device cap gives device_limit_reached.

Idempotency

Send Idempotency-Key: <string> on any POST. A replay of the same key within 24 h returns the stored original response — the same status and the same body — so a network retry can't create a duplicate job. There is no conflict to recover from; just retry with the same key.

Rate limiting

Metadata

Every mutable resource accepts "metadata": { "order_id": "12345", ... } — up to 20 keys, string values of 500 characters or less. Attach your own identifiers, then filter by them on any list endpoint: ?metadata[order_id]=12345. This is the intended way to correlate PrintSocket resources with records in your own system.

Versioning

The major version is in the path (/v1). Additive changes — new fields, new endpoints, new enum values — are not breaking and can land at any time, so parse defensively. Breaking changes would ship as /v2.

3. Resources

3.1 Utility

MethodPathNotes
GET/pingUnauthenticated liveness check. { "ok": true }
GET/meIntrospect the calling key: account, key id, scopes, and mode (live/test)

3.2 Devices

A device is a machine running the PrintSocket agent.

MethodPathNotes
GET/devicesFilters: state=online|offline, metadata[...]
GET/devices/{id}
PATCH/devices/{id}name, metadata
POST/devices/{id}/revokeInvalidates the agent's credentials. The device goes to state: revoked and cannot reconnect until it is enrolled again — reconnecting the machine will not bring it back.
POST/enrollment_tokensReturns a short-lived (1 h), single-use token for the agent installer. Body: optional name and metadata, pre-applied to the device when it enrolls.

Device object:

{
  "id": "dev_7hk2m",
  "object": "device",
  "name": "Front desk PC",
  "state": "online",            // online | offline | revoked
  "hostname": "FRONTDESK-01",
  "os": { "family": "windows", "version": "11" },
  "agent_version": "1.4.2",
  "last_seen_at": "2026-07-29T14:01:55Z",
  "created_at": "...",
  "metadata": {}
}

agent_version is the build the machine is running right now, not the one it enrolled with — it updates when the agent updates. os is recorded at enrollment time only.

3.3 Printers

Printers are discovered automatically by the agent. They are not created through the API.

MethodPathNotes
GET/printersFilters: device_id, state=online|offline, enabled=true|false, metadata[...]
GET/printers/{id}Includes full capabilities
PATCH/printers/{id}name (display alias), enabled (soft-disable: rejects new jobs), default_options, metadata

Printer object:

{
  "id": "prn_8f2k1",
  "object": "printer",
  "device_id": "dev_7hk2m",
  "name": "Label printer",          // your alias, PATCHable
  "system_name": "ZDesigner ZD420", // as reported by the OS
  "state": "online",                // online | offline (offline whenever the device is offline)
  "enabled": true,
  "default_options": { "copies": 1 },
  "capabilities": {
    "color": false,
    "duplex": false,
    "copies_max": 99,
    "dpis": ["203x203"],
    "papers": { "4x6": [1016, 1524] },   // tenths of a mm
    "trays": ["Main"],
    "media": [],
    "collate": true,
    "custom_paper_size": false
  },
  "created_at": "...",
  "metadata": {}
}

default_options merge underneath per-job options, so you can configure a printer once (always 203 dpi, tray 2) instead of repeating it on every job.

Removal. Agents report the complete queue list, so a printer missing from a report has been deleted from the OS — queues that are present but unreachable are still reported, as offline. A removed printer leaves every listing, GET /printers/{id} returns 404, and printer.removed fires. If the queue reappears under the same system_name, the printer revives with the same id and printer.discovered fires again.

3.4 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.

MethodPathNotes
POST/documentsEither a direct upload (content_type: application/pdf, body up to 50 MB) or { "source_url": "...", "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.

3.5 Jobs

MethodPathNotes
POST/jobsCreate (see below). Returns 201 with the full job object.
GET/jobsFilters: printer_id, device_id, status, created_after/created_before, metadata[...]
GET/jobs/{id}
POST/jobs/{id}/cancelCancels 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}/eventsOrdered state-transition timeline for the job

Create request (send Idempotency-Key as a header, not a body field):

{
  "printer_id": "prn_8f2k1",
  "title": "Order #12345 label",
  "content": {
    "format": "pdf",                 // pdf | raw (ZPL/ESC-POS/etc.)
    "url": "https://...",            // exactly one of url | base64 | document_id
    "base64": "...",
    "document_id": "doc_2ka9x",
    "fetch_auth": { "type": "basic", "username": "...", "password": "..." }  // only with url
  },
  "copies": 2,
  "options": {
    "duplex": "long_edge",           // long_edge | short_edge | none
    "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,          // false → rejected immediately with 422 printer_offline
  "expire_after_seconds": 3600,      // default 86400 (24 h)
  "metadata": { "order_id": "12345" }
}

Job object:

{
  "id": "job_5tq8p",
  "object": "job",
  "printer_id": "prn_8f2k1",
  "device_id": "dev_7hk2m",
  "title": "Order #12345 label",
  "status": "printing",
  "copies": 2,
  "options": { ... },
  "completion_confidence": null,     // set on success: "printer" | "spooler" (see below)
  "warnings": [                      // non-fatal notices; the job still proceeds
    { "code": "option_unsupported", "message": "duplex printing requested but the printer does not report duplex support." }
  ],
  "error": null,                     // { "code": "printer_error", "message": "Out of paper" } when failed
  "created_at": "...",
  "queued_at": "...",
  "sent_at": "...",
  "completed_at": null,
  "expires_at": "...",
  "metadata": { "order_id": "12345" }
}

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)

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 codeMeaning
option_unsupportedAn 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_maxcopies is greater than the printer's reported copies_max. Behavior beyond the maximum is driver-defined.

3.6 Scales

Read-only telemetry from USB scales attached to a device.

MethodPathNotes
GET/scalesFilter: device_id
GET/scales/{id}Latest reading embedded

Scale object:

{
  "id": "scl_9d3f2",
  "object": "scale",
  "device_id": "dev_7hk2m",
  "system_name": "Mettler Toledo PS60",
  "state": "online",                 // online | offline (offline whenever the device is offline)
  "reading": {                       // null until the agent reports one
    "weight_grams": 1234,
    "stable": true,
    "captured_at": "2026-07-29T14:02:08Z"
  },
  "created_at": "..."
}

Scales are discovered and reported by the agent, like printers. A report without a fresh reading keeps the previous one, so check captured_at to see how stale a weight is, and stable to know whether the scale had settled. Read the weight on demand at the moment you need it — typically when buying a label. Scales carry no metadata and emit no webhook events.

3.7 Webhooks

MethodPathNotes
POST/webhooks{ "url": "...", "events": ["job.*", "printer.state_changed"], "description": "..." } → returns the endpoint including its secret (shown once)
GET/webhooks and /webhooks/{id}
PATCH/webhooks/{id}url, events, disabled
DELETE/webhooks/{id}
POST/webhooks/{id}/testSends a synthetic ping event so you can verify your receiver

Event types, subscribable with wildcards:

Delivery payload:

{
  "id": "evt_3mm2z",
  "type": "job.succeeded",
  "created_at": "...",
  "data": { ...full job object... }
}

3.8 API keys

Requires the manage scope.

MethodPathNotes
GET/api_keysSecrets are never re-shown; returns the prefix and last 4 characters
POST/api_keys{ "name": "...", "scopes": ["print"], "mode": "live" } → secret shown once
DELETE/api_keys/{id}Immediate revocation

Account and billing management lives in the dashboard rather than this API.

4. Enrolling a device

  1. From your server, POST /enrollment_tokens{ "token": "enr_...", "expires_at": ... }.
  2. Run the agent installer on the target machine with that token (CLI flag or paste into the GUI).
  3. The agent exchanges the token for device-scoped credentials. The device appears as dev_... and device.enrolled fires.
  4. Printers are discovered automatically; a printer.discovered event fires for each.

Enrollment tokens are short-lived and single-use, and the credentials the agent ends up holding are scoped to that one device. Your account API keys never need to be embedded in an installer or stored on an end customer's machine.