Sealing content
How CatzPrint encrypts
| Where | How |
|---|---|
| Your server or app → CatzPrint | TLS. Optionally, content sealed to the server key with sealed_content. |
| Stored files | AES-256-GCM at rest. Job documents are deleted 30 days after the job finishes and uploads after 7 days, by default. |
| CatzPrint → station | TLS, and every job's content is sealed to that station's own X25519 key, created on the device when it was paired. Only that station can open it. |
A sealed box (libsodium crypto_box_seal) encrypts to a public key with a fresh, throwaway key pair, so the sender needs no secret of its own and the result can only be opened with the matching private key.
Seal a document
With an SDK, add sealed: true (sealed=True in Python, Sealed: true in Go, .sealed(true) in Rust) to any text, PDF, image or file job. The SDK fetches the server's public key once, seals the content on your machine and sends only the sealed box.
await cz.printPdf("invoice-2041.pdf", { role: "office", title: "Invoice 2041", sealed: true });
await cz.printText("Door code: 4512", { printerId, sealed: true });Rules and errors
- Send either
contentorsealed_content, not both.sealed_contentwins if both are present. - Commands and template data are JSON fields and can't be sealed; they are still protected by TLS and sealed to the station.
- If the server can't open the box you get
400 invalidonsealed_content: Couldn't decrypt. Seal it to the server's current public key. Fetch the key again and retry — keys can be rotated.
Without an SDK
Fetch the key from GET /v1/public/server-key. It needs no authentication; the key is base64 (standard alphabet) and 32 bytes long.
curl https://api-v1.catzprint.com/v1/public/server-key
{
"public_key": "tYVhWIiQaqL3O8b85W6Ed1yUI9NG3gXEFqRIhMFX8xQ=",
"algorithm": "x25519-xsalsa20-poly1305-sealed-box"
}Seal the exact bytes you would otherwise base64-encode into content — the PDF, image or Office file, or the UTF-8 text for kind: text. Base64-encode the sealed result and send it as sealed_content. Set kind as usual.
import fs from "node:fs";
import sodium from "libsodium-wrappers";
const API = "https://api-v1.catzprint.com";
await sodium.ready;
// 1. Fetch the server's public key (cache it for a few minutes at most).
const { public_key } = await (await fetch(`${API}/v1/public/server-key`)).json();
const serverKey = sodium.from_base64(public_key, sodium.base64_variants.ORIGINAL);
// 2. Seal the raw document bytes — not base64 — to that key.
const pdf = fs.readFileSync("invoice-2041.pdf");
const sealed = sodium.crypto_box_seal(pdf, serverKey);
// 3. Send it as sealed_content instead of content.
const res = await fetch(`${API}/v1/orgs/${process.env.CATZ_ORG}/jobs`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.CATZ_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
kind: "pdf",
role: "office",
title: "Invoice 2041",
sealed_content: sodium.to_base64(sealed, sodium.base64_variants.ORIGINAL),
}),
});crypto_box_seal_open.