Realtime MQTT
Receive realtime printer status updates and entity change events via MQTT. This is the same data that powers the Printago dashboard.
Connection Details
mqtts://realtime.printago.io:8883wss://realtime.printago.ioapiclient_{keyId}_{suffix} where keyId is the first 24 characters of your API key and suffix is a random value unique to each client instance, generated once at startup and reused across reconnects. Re-using a fixed suffix across processes or tabs makes two connections disconnect each other in an endless reconnect loop.Permissions
A key's subscriptions are filtered by its permissions — you only receive messages for the topics and entity types the key is allowed to view.
Realtime Printer Stats
Subscribe to receive realtime printer status updates including temperatures, print progress, filament info, and more.
stores/{storeId}/printer-stats/#
stores/{storeId}/printer-stats/{printerId}
{
printerId: string;
storeId: string;
state: string | null; // e.g. "idle", "printing", "paused"
isOnline: boolean;
isAvailable: boolean | null;
receivedAt: string; // ISO date
expiresAt: string; // ISO date
data: {
// Payload shape version. Older or retained messages may omit it and use
// the legacy temps / short fan+light keys documented below.
version?: number;
// Temperatures indexed by component ref (e.g. "nozzle_0", "bed_0", "chamber_0")
temperatures?: {
[ref: string]: { current?: number; target?: number };
};
// Legacy flat temperatures, superseded by temperatures above. May still
// appear on older or retained messages; prefer temperatures when present.
temps?: {
bedTemp?: number;
bedTempTarget?: number;
nozzleTemp?: number;
nozzleTempTarget?: number;
chamberTemp?: number;
chamberTempTarget?: number;
};
// Fan speeds (0-100) indexed by fan ref (e.g. "fan_part", "fan_aux",
// "fan_chamber"). Legacy messages may use short keys: part / aux / chamber.
fans?: { [ref: string]: number };
// Light states indexed by light ref (e.g. "light_work", "light_chamber").
// Legacy messages may use short keys: work / chamber.
lights?: { [ref: string]: "on" | "off" };
// Active nozzle / tool ref (multi-nozzle & toolchanger printers)
activeNozzle?: string; // e.g. "nozzle_0"
activeTool?: string; // e.g. "tool_0"
// Runtime peripheral state grouped by capability ref
peripherals?: { [ref: string]: PeripheralInstanceState[] };
printer?: {
nozzleDiameter: string;
homeAxes: {
x?: boolean;
y?: boolean;
z?: boolean;
};
};
print?: {
downloadPercent?: number;
percent?: number;
timeRemaining?: number; // seconds
stage?: string;
activity?: string;
errorCode?: string;
printError?: number;
currentLayer?: number;
totalLayers?: number;
taskId?: string;
taskName?: string;
printJobId?: string; // Printago print job id for the running print, when it can be matched
skippedObjects?: number[]; // Object ids skipped at runtime
};
filament?: {
// Multi-material system (AMS / ERCF / MMU / toolchanger)
ams?: {
status: {
slotsLoaded: string; // Bit string of loaded slots (legacy; prefer per-slot loaded)
slotsReading: string; // Bit string of slots currently being read
type?: "ams" | "ercf" | "mmu" | "toolchanger" | "manual";
};
ams: {
amsId?: number;
name?: string;
type?: string; // "AMS" | "AMS_LITE" | "N3S" | "ERCF" | ...
slots: {
id: string;
loaded?: boolean; // Slot has filament loaded
reading?: boolean; // Slot is currently being read
active?: boolean; // Slot currently feeding the toolhead
// Filament data (present once the slot has been read):
color?: string; // Hex, e.g. "#FF5733" or "#FF5733FF"
type?: string; // e.g. "PLA", "PETG", "ABS"
info?: string;
tagId?: string;
detected?: boolean; // Material came from an RFID/NFC read, not a manual assignment
remaining?: number; // Percentage remaining
brand?: string;
name?: string;
v?: string;
}[];
}[];
};
// External / direct-feed spools, one entry per spool
spools?: {
id?: string;
color: string;
type: string; // e.g. "PLA"
info: string;
tagId?: string;
detected?: boolean;
remaining: number;
loaded?: boolean;
brand?: string;
name?: string;
v?: string;
}[];
};
// Staggered Start: target temperatures being waited on before this
// printer starts, reached when temperatures[ref].current >= target.
holds?: {
bed?: { ref: string; target: number };
nozzle?: { ref: string; target: number };
};
health?: {
errors: { message: string; code: string; }[];
warnings: { message: string; code: string; }[];
infos: { message: string; code: string; }[];
};
// Vendor / connection specific blocks (present only for that printer type)
klipper?: {
speedFactor?: number; // 1.0 = 100%
extrudeFactor?: number; // 1.0 = 100%
pressureAdvance?: number;
currentGcodeLine?: number;
};
prusa?: {
flow?: number; // percent, 100 = no override
speed?: number; // percent, 100 = no override
axisX?: number;
axisY?: number;
axisZ?: number;
};
fuse?: {
status: "downloading" | "uploading" | "starting" | "idle" | "error";
download?: { percent: number; bytesTransferred?: number; totalBytes?: number };
upload?: {
percent: number;
bytesTransferred?: number;
totalBytes?: number;
attempt?: number;
maxAttempts?: number;
};
error?: string;
};
camera?: {
lastSnapshotAt?: number; // Unix ms of the most recent snapshot upload
streamingAvailable?: boolean;
snapshotsAvailable?: boolean;
snapshotCadenceMs?: number;
timelapsesAvailable?: boolean;
};
}
}
Entity Change Events
Subscribe to receive notifications when data changes in your account (printers, parts, orders, etc.).
stores/{storeId}/entities/#
stores/{storeId}/entities/printers
Available entity types:
{
storeId: string; // Store where change occurred
entityType: string; // Entity type (e.g., "printers", "parts")
action: "INSERT" | "UPDATE" | "DELETE";
entities: {
[entityId: string]: EntityData | null // Full entity or null for DELETE
};
changes: { // Only present for UPDATE operations
[entityId: string]: {
[fieldName: string]: {
old: any; // Previous value
new: any; // New value
}
}
}
}
Slicer Progress
Subscribe to receive realtime progress updates while a print job is being sliced. Each message reports per-plate and overall percentage, plus the current stage. Useful for showing a live progress bar between the moment a job is submitted and the moment a sliced G-code is ready to send to a printer.
Messages are retained, so a fresh subscriber immediately receives the latest progress for any in-flight job on that topic. Updates are throttled to roughly one message every two seconds per job. When slicing finishes, an empty payload is published to the same topic to clear the retained state — your client should treat an empty payload as "no active slicing" and ignore it for UI purposes.
stores/{storeId}/builders/slicer/progress/job/#
stores/{storeId}/builders/slicer/progress/job/{printJobId}
{
printJobId: string; // The print job currently being sliced
plateIndex: number; // Current plate being sliced (0-indexed)
plateCount: number; // Total number of plates in this job
platePercent: number; // Progress for the current plate (0-100)
totalPercent: number; // Overall progress across all plates (0-100)
message?: string; // Current stage/operation, e.g. "Generating support"
warning?: string; // Non-fatal warning from the slicer, if any
}
The printJobId in the topic matches the id of the corresponding print_jobs entity. Pair this stream with an entities/print_jobs subscription to know when slicing started, succeeded, or failed.
Example: Node.js Connection
import mqtt from 'mqtt';
const STORE_ID = 'your_store_id';
const API_KEY = 'your_api_key';
const KEY_ID = API_KEY.substring(0, 24); // First 24 chars of API key
const PRINTER_ID = 'cm7abc123def456'; // Your printer's Printago ID
// Unique per client instance. Generate once at startup and reuse it across
// reconnects — two connections sharing a Client ID disconnect each other.
const SUFFIX = Math.random().toString(36).slice(2, 10);
const client = mqtt.connect('wss://realtime.printago.io', {
username: STORE_ID,
password: API_KEY,
clientId: `apiclient_${KEY_ID}_${SUFFIX}`
});
client.on('connect', () => {
console.log('Connected to MQTT');
// Subscribe to a specific printer's stats
client.subscribe(`stores/${STORE_ID}/printer-stats/${PRINTER_ID}`);
// Or use # wildcard for all printers: `stores/${STORE_ID}/printer-stats/#`
// Subscribe to part changes
client.subscribe(`stores/${STORE_ID}/entities/parts`);
// Subscribe to slicer progress for all jobs
client.subscribe(`stores/${STORE_ID}/builders/slicer/progress/job/#`);
});
client.on('message', (topic, message) => {
// Slicer progress publishes an empty payload to clear retained state
if (message.length === 0) return;
const data = JSON.parse(message.toString());
if (topic.includes('printer-stats')) {
console.log(`Printer ${data.printerId}: ${data.state}, ${data.data?.print?.percent ?? 0}%`);
} else if (topic.includes('entities/parts')) {
console.log(`Parts ${data.action}:`, Object.keys(data.entities));
} else if (topic.includes('builders/slicer/progress')) {
console.log(`Slicing ${data.printJobId}: ${data.totalPercent}% (${data.message ?? ''})`);
}
});