Node.js client for the PrintSocket cloud print API.
A lightweight agent runs on a machine, connects outbound to PrintSocket, and exposes that machine's printers (and scales) to a REST API. This library wraps API v1: devices, printers, scales, documents, print jobs, webhooks, and API keys, plus webhook signature verification for your receiver.
Zero runtime dependencies. Requires Node.js 18.17 or newer. Full API documentation lives at www.printsocket.com/docs.
npm install printsocketAn sk_test_ key comes with a virtual device and printer that runs the full
job lifecycle, so this works before any hardware is enrolled:
import PrintSocket from "printsocket";
const ps = new PrintSocket({ apiKey: process.env.PRINTSOCKET_API_KEY });
const { data: printers } = await ps.printers.list({ state: "online" });
const job = await ps.jobs.create({
printer_id: printers[0].id,
title: "Order #12345 label",
content: { format: "pdf", url: "https://example.com/label.pdf" },
metadata: { order_id: "12345" },
});
console.log(job.id, job.status); // job_... queuedCommonJS works too:
const { PrintSocket } = require("printsocket");const ps = new PrintSocket({
apiKey: "sk_live_...", // required
baseUrl: "https://api.printsocket.com/v1", // default
timeout: 30_000, // ms per attempt
maxRetries: 2, // connection failures, 429s, and 5xx
});Every API error throws a typed subclass of APIError carrying status,
type, code, param, and requestId (quote the request id in support
requests):
import { BillingError, ConflictError, NotFoundError } from "printsocket";
try {
await ps.jobs.cancel(jobId);
} catch (err) {
if (err instanceof ConflictError && err.code === "job_not_cancelable") {
// already printing or finished
} else {
throw err;
}
}The classes are InvalidRequestError, AuthenticationError,
PermissionError, NotFoundError, ConflictError, RateLimitError,
BillingError, and ServerError, one per error.type the API returns.
Requests that never got a response throw APIConnectionError.
Connection failures, 429s, and 5xx responses are retried automatically
(maxRetries, default 2), honoring Retry-After. Every POST carries an
Idempotency-Key header, generated when you do not pass one, and the key is
identical across the client's own retry attempts, so a retried create cannot
produce a duplicate job. To extend the guarantee across your own retries,
pass a key derived from your record:
await ps.jobs.create(params, { idempotencyKey: `order-12345-label` });List calls return one page (data, has_more, next_cursor). Each list
resource also has iterate(), which follows cursors for you:
for await (const job of ps.jobs.iterate({ status: "failed", limit: 100 })) {
console.log(job.id, job.error?.message);
}Upload once, print many times:
import { readFile } from "node:fs/promises";
const doc = await ps.documents.upload({
content: await readFile("packing-slip.pdf"),
content_type: "application/pdf",
expire_after_seconds: 3600,
});
await ps.jobs.create({
printer_id: "prn_...",
content: { format: "pdf", document_id: doc.id },
});documents.createFromURL({ source_url }) has the API fetch the file
server-side instead.
Subscribe with the client, verify deliveries with the webhooks helpers.
Verification needs the raw request body; a parsed and re-serialized body will
not match the signature.
import { webhooks, WebhookVerificationError } from "printsocket";
import express from "express";
const endpoint = await ps.webhooks.create({
url: "https://example.com/printsocket/webhook",
events: ["job.*", "printer.state_changed"],
});
// endpoint.secret is shown only this once; store it.
const app = express();
app.post(
"/printsocket/webhook",
express.raw({ type: "application/json" }),
(req, res) => {
let event;
try {
event = webhooks.constructEvent(
req.body,
req.headers["printsocket-signature"],
process.env.PRINTSOCKET_WEBHOOK_SECRET,
);
} catch (err) {
if (err instanceof WebhookVerificationError) return res.sendStatus(400);
throw err;
}
// Delivery is at-least-once: dedupe on event.id before acting.
console.log(event.type, event.data.id);
res.sendStatus(200);
},
);Generate a short-lived, single-use token server-side and hand it to the agent installer, so your API keys never touch a customer machine:
const { token } = await ps.enrollmentTokens.create({ name: "Front desk PC" });const scale = await ps.scales.get("scl_...");
if (scale.reading?.stable) {
console.log(scale.reading.weight_grams, "g at", scale.reading.captured_at);
}Written in TypeScript; types ship with the package. Wire fields are snake_case, matching the API reference exactly. The API adds fields and enum values without a version bump, so string unions are deliberately open.
npm install
npm test # builds ESM + CJS, then runs node --testMIT