Authentication and conventions

Everything on this page is true of every endpoint: how you authenticate, what a request and a response look like, how lists page, and what an error actually tells you. Reading it once is what keeps the resource pages short.

Two things here are worth knowing before you write any code. Test mode gives you a virtual device and printer that runs the full job lifecycle, so an integration can be built and verified end to end before any hardware exists. And every response carries a Request-Id, which is the one value support needs to find your call in the logs.

Keys and scopes

  • Authorization: Bearer <key> on every request except /ping.
  • Keys look like sk_live_... or sk_test_.... The prefix identifies the mode. Secrets are shown once, at creation.
  • Each key carries a scope: read (GET only), print (read + create/cancel jobs), or manage (everything, including key and webhook management).

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. Jobs sent to the virtual printer never count toward plan limits, on any plan.

Test-mode data is completely separate from live data: a test-mode key cannot see, list, or print to anything in the live pool, or the reverse. That makes test mode the right place to enroll a real printer you are experimenting with, since it can never appear in the fleet your production code enumerates. Real hardware bills the same in either pool, though: a job to a real printer counts whichever mode it was created in, because the paper is real either way. Your first two real devices in the test pool are exempt from the device limit, so checking an integration against hardware costs you nothing.

Utility

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

Requests and responses

  • JSON only (application/json), snake_case fields, timestamps in RFC 3339 UTC (2026-07-29T14:02:11Z).
  • Durations are always explicit-unit fields (expire_after_seconds), never bare numbers.
  • Every response carries a Request-Id header, and every error echoes it in the body. Quote it in support requests.
  • IDs are opaque and prefixed by type: dev_ device, prn_ printer, job_ job, doc_ document, whk_ webhook, key_ API key, scl_ scale. Treat them as strings; don't parse them.

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 either mode: creating a job to a real printer 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

  • Standard headers on every response: RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset.
  • Sustained excess returns 429 with Retry-After. The default is 20 requests/second per account, burstable.

Metadata

Every mutable resource accepts "metadata": { "order_id": "12345", ... }, with up to 20 keys and 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.

Back to the docs index