API Documentation

Send print jobs from any web app, server, or script to printers connected through the PrintRelay Mac agent.

Base URL: https://print.jiayun.ai
API version: v1 (prefix /api/public/v1)
Format: JSON over HTTPS

1 · Authentication

Every request must include a Bearer token in the Authorization header. Create an API key in Dashboard → API Keys. Keys look like prk_live_xxxxxxxx….

Authorization: Bearer prk_live_xxxxxxxx

For drop-in compatibility with tools written for PrintNode, HTTP Basic is also accepted (API key as username, empty password):

Authorization: Basic <base64(prk_live_xxxxxxxx:)>

Scopes, rate limits & IP allowlist

  • Each key carries scopes: read (list/get), print (create/cancel jobs), manage (everything). Default is read,print.
  • Optional per-key rate limit (default 600/min). Exceeding it returns 429.
  • Optional IP allowlist — requests from other IPs return 403.
  • Never embed keys in browser or mobile code. Call this API from your backend only.
  • Revoke compromised keys immediately in Dashboard → API Keys.

2 · Errors

All errors use this shape:

{
  "error": {
    "message": "Human-readable explanation",
    "code": 401
  }
}

Validation errors (HTTP 400) include a details field with Zod-style field errors.

HTTPMeaning
200OK
201Created (new print job)
400Validation failed
401Missing, malformed, invalid, or revoked API key
403Missing required scope, or IP not in allowlist
404Not found, or not owned by you
409Conflict (e.g. cancel a job that already finished)
429Rate limit exceeded for this key
500Internal server error

3 · Endpoints

All endpoints share the prefix https://print.jiayun.ai/api/public/v1.

GET /whoami

GET/api/public/v1/whoami

Verify your API key and return the owning account. Use this as a health check.

curl https://print.jiayun.ai/api/public/v1/whoami \
  -H "Authorization: Bearer prk_live_xxxxxxxx"

Response 200

{
  "id": "d9b1e6ea-7c21-4924-9727-5a12e2f0b826",
  "displayName": "Jia Wei",
  "company": "Jiayun"
}

GET /computers

GET/api/public/v1/computers

List Mac agents bound to your account. state is online when the agent's WebSocket is currently connected.

[
  {
    "id": "9be2a2fd-b3c2-4077-8605-309788806b41",
    "name": "kgjs-MacBook-Pro",
    "hostname": "kgjs-MacBook-Pro.local",
    "os": "darwin",
    "state": "online",
    "client_version": "1.0.0",
    "last_seen_at": "2026-05-29T12:40:11Z",
    "created_at": "2026-05-29T11:02:33Z"
  }
]

GET /printers

GET/api/public/v1/printers

List the printers exposed by all of your computers. Jobs sent to an offline printer are queued and delivered on reconnect.

curl https://print.jiayun.ai/api/public/v1/printers \
  -H "Authorization: Bearer prk_live_xxxxxxxx"

Response 200

[
  {
    "id": "88c9a3b3-7e92-4a53-a82a-c6651f925312",
    "name": "HP_LaserJet_M109_M112",
    "is_default": true,
    "state": "online",
    "capabilities": {},
    "computer_id": "9be2a2fd-b3c2-4077-8605-309788806b41"
  }
]

POST /printjobs

POST/api/public/v1/printjobs

Submit a print job.

FieldTypeReq.Notes
printerIduuidyesFrom GET /printers
titlestring (1–200)yesShown in dashboard
contentTypeenumyespdf_uri | pdf_base64 | raw_base64 | image_uri | image_base64 | url
contentstringyes≤ ~20 MB base64
optionsobjectnocopies, paperSize, orientation, duplex, color, scale, tray, pageRanges, dpi
sourcestring (≤80)noFree-form tag (defaults to API key label)

Pass Idempotency-Key: <your-id> as a header to safely retry without duplicate prints — same key returns the original job for 24h. Cancel a queued/sent job with DELETE /api/public/v1/printjobs/{id}.

Webhooks: configure endpoints in Dashboard → Webhooks to receive job.queued, job.sent, job.printing, job.done, job.error, job.cancelled, printer.online, printer.offline. Verify each request with X-PrintRelay-Signature: t=<ts>,v1=hmac_sha256(secret,"{ts}.{body}").

Sandbox testing: create a virtual printer in Dashboard → Printers to integrate against the full job lifecycle without installing the desktop agent.

Example — print a PDF by URL

curl -X POST https://print.jiayun.ai/api/public/v1/printjobs \
  -H "Authorization: Bearer prk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "printerId": "88c9a3b3-7e92-4a53-a82a-c6651f925312",
    "title": "Invoice #1234",
    "contentType": "pdf_uri",
    "content": "https://example.com/files/invoice-1234.pdf",
    "source": "billing"
  }'

Response 201

{
  "id": "9f1c0a44-1f7b-4d2b-8d61-7c2a3a8c4c1e",
  "title": "Invoice #1234",
  "state": "sent",           // "sent" if agent online, "queued" if offline
  "created_at": "2026-05-29T12:40:00Z"
}

Example — Node.js (in-memory PDF)

import fs from "node:fs";

const pdfBase64 = fs.readFileSync("/tmp/invoice.pdf").toString("base64");

await fetch("https://print.jiayun.ai/api/public/v1/printjobs", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PRINT_JIAYUN_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    printerId,
    title: `Order ${orderId}`,
    contentType: "pdf_base64",
    content: pdfBase64,
    source: "orders-service",
  }),
});

Example — Python

import base64, requests

with open("invoice.pdf", "rb") as f:
    content = base64.b64encode(f.read()).decode()

r = requests.post(
    "https://print.jiayun.ai/api/public/v1/printjobs",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "printerId": PRINTER_ID,
        "title": "Invoice 1234",
        "contentType": "pdf_base64",
        "content": content,
        "source": "billing",
    },
)
r.raise_for_status()
print(r.json())

Example — receipt printer (raw ESC/POS)

const raw = Buffer.from(escposBuffer).toString("base64");
await fetch("https://print.jiayun.ai/api/public/v1/printjobs", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    printerId,
    title: "Receipt #92",
    contentType: "raw_base64",
    content: raw,
  }),
});

GET /printjobs

GET/api/public/v1/printjobs?limit=50

List recent jobs (most recent first). limit default 50, max 100.

curl "https://print.jiayun.ai/api/public/v1/printjobs?limit=20" \
  -H "Authorization: Bearer prk_live_xxxxxxxx"

Response 200

[
  {
    "id": "9f1c0a44-1f7b-4d2b-8d61-7c2a3a8c4c1e",
    "title": "Invoice #1234",
    "state": "done",
    "source": "billing",
    "content_type": "pdf_uri",
    "created_at": "2026-05-29T12:40:00Z",
    "printed_at": "2026-05-29T12:40:03Z",
    "printer_id": "88c9a3b3-7e92-4a53-a82a-c6651f925312",
    "error": null
  }
]

4 · Print job lifecycle

StateSet byMeaning
queuedserverStored; waiting for the agent to come online
sentserverPushed over WebSocket to the agent
printingagentHanded to the OS print spooler
doneagentPrint completed; printed_at is set
erroragentFailed; see error field
cancelledagentCancelled before printing

5 · Content type guide

contentTypecontent must beBest for
pdf_uriPublic HTTPS URL to a PDFPDFs hosted on your server / CDN
pdf_base64Base64-encoded PDF bytesPDFs generated in memory; private docs
raw_base64Base64 raw printer bytesThermal receipts (ESC/POS), labels (ZPL/TSPL)

Hard limit on content: ~20 MB of base64 text (≈ 15 MB binary). For pdf_uri, the URL must be reachable from the Mac agent's network — the agent fetches it, not the API server.

6 · Integration checklist

  1. Install the Mac agent and bind it from Dashboard → Computers.
  2. Create an API key in Dashboard → API Keys; store it server-side.
  3. Smoke test: GET /whoami → expect HTTP 200.
  4. Discover printers: GET /printers; cache IDs or show a picker.
  5. Submit jobs: POST /printjobs from your backend.
  6. (Optional) Poll GET /printjobs and show state to your users.
  7. Monitor: if GET /computers reports offline for long, alert someone.

7 · PrintNode compatibility

Migrating from PrintNode? Most code paths translate directly:

Need help?

Contact us at support@jiayun.ai with the request ID from your 4xx/5xx response.