Printago API

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 Endpoint
mqtts://realtime.printago.io:8883
WebSocket Endpoint
wss://realtime.printago.io
Username
Your store ID
Password
Your API key (without the "ApiKey " prefix)
Client ID
Format: apiclient_{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.

Permission changes take up to 6 hours to reach MQTT. The broker caches each key's topic access, so granting or removing a permission on a key that is already connected can take up to 6 hours to take effect. Disconnecting and reconnecting does not clear the cache. Deactivating or deleting a key blocks new connections within about an hour; to cut off an active session immediately, contact support.

Realtime Printer Stats

Subscribe to receive realtime printer status updates including temperatures, print progress, filament info, and more.

Topic (all printers)
stores/{storeId}/printer-stats/#
Topic (single printer)
stores/{storeId}/printer-stats/{printerId}
Message Format
{
  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.).

Topic (all entities)
stores/{storeId}/entities/#
Topic (specific entity type)
stores/{storeId}/entities/printers

Available entity types:

addon_subscriptions addon_usage_records api_keys cost_components customers entitlements folders ignored_skus integrations maintenance_completion_logs maintenance_items material_group_members material_groups material_profile_assignments material_variants materials order_items orders part_build_steps part_builds part_material_assignments parts print_jobs printer_maintenance_enrollments printer_slots printer_throttle_group_members printer_throttle_groups printer_throttle_leases printers profiles settings_notifications settings_store sku_builds sku_costs sku_option_bindings sku_option_properties sku_option_property_values sku_option_value_filters sku_option_values sku_options sku_parts skus slicer_jobs subscriptions user_permissions
Message Format
{
  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.

Topic (all jobs)
stores/{storeId}/builders/slicer/progress/job/#
Topic (single job)
stores/{storeId}/builders/slicer/progress/job/{printJobId}
Message Format
{
  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

JavaScript
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 ?? ''})`);
  }
});