This reference describes the Bambu firmware 3.10.4 implementation. It does not describe a Prusa firmware API. The state response is shared by HTTP and WebSocket clients; it is not a separately versioned public schema. Ignore unknown fields and check firmware_version when supporting several releases.
The examples use the documentation address 192.0.2.42. Replace it with your HAT's address. The GET examples only read data. See Developer reference for connection and request rules.
curl --fail --show-error --max-time 5 \
-H "Accept: application/json" \
http://192.0.2.42/api/state
GET /api/state returns application/json with Cache-Control: no-store. It returns a full snapshot, not a delta. A low-memory failure returns HTTP 503 with {"error":"State memory unavailable"}. Common address/origin failures return 403 as described in the developer overview.
| Field | Type | Meaning |
|---|---|---|
hardware_serial |
string | HAT identity, normally beginning TH-; use this to distinguish controllers |
device_serial |
string | Configured printer serial, not the HAT serial |
firmware_version |
string | Running application version |
hostname, network_ip, network_name |
string | Controller hostname, current address, and network name |
boot_id |
string | Identifier generated at startup; scope sequence comparisons to this boot |
state_seq |
integer | Increases whenever the firmware builds a snapshot; gaps are expected |
uptime_ms |
number | Milliseconds since controller startup |
ack_id |
integer, optional | Command correlation ID in an acknowledged WebSocket reply; ordinary GET responses omit it |
persistence_pending, persistence_error |
boolean | General settings still need saving, or a save failed |
sta_connected, mqtt_connected |
boolean | Wi-Fi station and Bambu MQTT connection state |
mqtt_status |
string | Human-readable connection status code |
printer_model, dual_hotend |
string, boolean | Configured model and whether a second hotend object is present |
Do not compare state_seq across boots. Within a boot, discard snapshots whose sequence is less than or equal to the last accepted sequence. This prevents a delayed HTTP reply from overwriting newer WebSocket state. A command acknowledgement must still be matched to its pending request even when its snapshot is older. After reconnecting or detecting a different boot_id, fetch fresh state and discard pending assumptions about prior commands.
This JavaScript example performs a single read from a Node.js process with fetch available. Run it from a local integration, or use the same request on the HAT's own origin; the firmware does not allow arbitrary cross-origin browser requests.
const response = await fetch('http://192.0.2.42/api/state', {
headers: { Accept: 'application/json' },
signal: AbortSignal.timeout(5000)
});
if (!response.ok) throw new Error(await response.text());
const state = await response.json();
console.log({
hat: state.hardware_serial,
firmware: state.firmware_version,
chamber: state.chamber_temp_valid ? state.chamber_temperature : null,
bed: state.bed_temp_valid ? state.bed_temperature : null
});
Temperatures and setpoints below are in degrees Celsius. null means unavailable, not zero. Test the associated validity field before using a reading. In particular, chamber_temperature remains a numeric field even when chamber_temp_valid is false.
| Fields | Type | Meaning |
|---|---|---|
chamber_temperature, chamber_temp_valid |
number, boolean | Chamber sensor reading and validity |
chamber_setpoint, max_setpoint |
integer, integer | Requested chamber target and selected allowed maximum; maximum is 60, 90, or 108 |
heater_output, chamber_relay_output |
boolean | Chamber heat demand and relay output state |
nozzle_temperature, nozzle_temp_valid |
number or null, boolean | Local hotend RTD reading |
nozzle_status, nozzle_temp_age_ms |
string, number or null | Local hotend sensor status and sample age |
printer_nozzle_temperature, printer_nozzle_target |
number or null | Temperature and target reported by Bambu |
printer_nozzle_temp_valid, printer_nozzle_target_valid |
boolean | Whether those Bambu values are currently usable |
printer_nozzle_temp_status, printer_nozzle_target_status |
string | Freshness/connection status for each Bambu value |
printer_nozzle_temp_age_ms, printer_nozzle_target_age_ms |
number or null | Individual Bambu sample ages |
bed_temperature, bed_temp_valid |
number or null, boolean | Local bed RTD reading |
bed_sensor_status, bed_sensor_age_ms |
string, number | Bed sensor status and sample age; age is -1 before any sample |
printer_bed_temperature, printer_bed_target |
number or null | Bed temperature and target reported by Bambu |
printer_bed_temp_valid, printer_bed_target_valid |
boolean | Freshness of each Bambu bed value |
printer_bed_temp_status, printer_bed_target_status |
string | Bed report freshness status |
bed_actual_target, bed_expected_temperature |
number or null | Local control target and expected mapped printer readback |
scaler_actual_target, scaler_expected_temperature |
number or null | Local hotend target derived from the active mapping, and expected printer readback |
For X2D, the top-level hotend fields describe the left hotend. When dual_hotend is true, right_hotend contains the same hotend field names for the right side, including nozzle_temperature, printer_nozzle_target, scaler_enabled, scaler_maximum, and nozzle_profiles. There is no left_hotend object. Do not use the left values as a fallback for a missing right object.
| Fields | Type | Meaning |
|---|---|---|
scaler_enabled, scaler_calibrated, scaler_calibrating, scaler_saving, scaler_fault |
boolean | Hotend mapping and calibration state |
scaler_status, scaler_error |
string | Hotend status and latest error text |
scaler_maximum, scaler_active_maximum, scaler_preset_pending |
number, number, boolean | Selected preset, applied preset, and a pending change |
hotend_installed, hotend_enable_remembered, hotend_restore_pending |
boolean | Installed setting and remembered/starting mapping state |
hotend_restore_wait |
string | Reason mapping has not yet resumed |
enable_save_pending, enable_save_failed |
boolean | Persistence of the selected channel's enable preference |
bed_installed, bed_enabled, bed_calibrated, bed_calibrating, bed_saving, bed_fault |
boolean | Bed hardware, control, calibration, and fault state |
bed_status, bed_error |
string | Bed status and latest error text |
bed_maximum, bed_control_mode |
number, string | Selected bed mapping maximum and bang_bang or pid regulation |
bed_heater_output, bed_relay_output, bed_power_percent |
boolean, boolean, number | Bed heat demand, relay state, and power percentage |
supply_voltage, bed_max_power, chamber_max_power |
number | Supply setting and configured output limits |
chamber_fan_on, chamber_fan_duty, chamber_fan_fault |
boolean, number, boolean | Fan output status and duty percentage |
printer_idle, hotend_calibration_busy |
boolean | Fresh printer idle indication and any hotend calibration work |
bed_error_recovery_pending, bed_error_recovery_holding_target |
boolean | Bed error recovery state |
bed_error_recovery_status, bed_error_classification |
string | Recovery status and bed error classification |
bed_error_recovery_attempts, bed_error_recovery_sent |
number | Recovery attempt and sent-command counters |
These are the most useful integration fields, not a complete list of low-level diagnostics. A temperature request, a remembered enable setting, an active controller, and an energized output are separate states; do not infer one from another. See Dashboard and Calibration and backups for their operational meaning.
print_job is an object containing configured, fresh, name, state, revision, thumbnail_revision, thumbnail_status, controls_ready, control_pending, and control_error. Its percent, remaining_minutes, layer, and layers are numbers or null when unknown. Check fresh before treating cached job data as current.
When thumbnail_revision is greater than zero, request GET /api/print/thumbnail?id=<thumbnail_revision>. A matching available image returns image/png with Cache-Control: no-store; an unavailable or outdated image returns 404. A busy background page/image transfer can return 503 with Retry-After: 1. Fetch again when the revision changes rather than continuously downloading the same image.
The firmware advertises an HTTP service through mDNS on UDP 5353:
| Item | Value |
|---|---|
| Service type | _http._tcp.local. |
| Service instance / hostname prefix | TYSON_HAT_ (compare without case sensitivity) |
| HTTP port | 80 |
| Identification request | GET /api/state, reading hardware_serial and firmware_version |
Resolve the service's IPv4 address and port, then confirm the identity through HTTP. The shipped Discovery app accepts a hardware_serial starting with TH- and a nonempty firmware version. These identify a device; they are not cryptographic authentication. The firmware does not publish a dedicated _tyson service or a separate Tyson UDP broadcast protocol. It does not offer a subnet-scan API.
The Discovery app implements this flow. A known IP can still work when multicast discovery is unavailable. The hostname reported by state can also be resolved with .local on systems that support mDNS.
GET /api/printers reads the HAT's fresh Bambu printer discovery cache. It discovers printers, not other HATs, and does not change the selected printer.
curl --fail --show-error --max-time 5 http://192.0.2.42/api/printers
The JSON shape is:
{
"wifi_connected": true,
"listeners": 2,
"printers": [
{"name": "Example printer", "serial": "EXAMPLE-SERIAL", "ip": "192.0.2.43", "model": "P2S"}
]
}
This is illustrative data. listeners is a numeric listener count, not a port number. The HAT's Bambu discovery listeners use UDP 1990 and 2021. An empty printers array means no fresh entries are available; it does not prove there are no printers on the network.
GET /api/wifi/scan returns the current scan result without starting a scan:
{"running":false,"options":"<option value=''>Click Scan Networks</option>"}
options is an escaped HTML <option> fragment used by the dashboard, not a structured network array. Actual results contain SSID values and display text with RSSI and open/secured status, with at most 40 scanned records. Treat this as a dashboard compatibility endpoint, not a stable Wi-Fi inventory schema.
To request a scan, send POST /wifi/scan with an empty form body, X-Tyson-Request: 1, Content-Type: application/x-www-form-urlencoded, and Accept: application/json. This starts asynchronous scan work and returns state; without the JSON Accept header it redirects to /settings. Poll GET /api/wifi/scan until running becomes false. The corresponding full-state flag is wifi_scan_running. A repeated start while running does not queue another scan.
GET /api/diagnostics/trace returns trace metadata as application/json, including state, boot_id, firmware_version, reason, capture, count, capacity, overwritten, trigger_us, first_us, last_us, schema, and post_limited. This firmware emits schema 1; trace time fields ending _us use microseconds.
The possible state values are recording, capturing, frozen, and unavailable. When the trace is frozen, request pages using both query parameters:
GET /api/diagnostics/trace?capture=<capture>&offset=0
capture must be an integer from 1 through 2147483647. offset must be from 0 through 4096. Each successful page contains at most 32 events, plus offset and next. Continue using next until it equals count. An event has this positional layout:
[microseconds, sequence, kind, task, a, b, c, d]
The final four values are event-specific diagnostic integers, not temperatures or a general command API. Preserve the schema and metadata with a capture. A changed capture or non-frozen trace returns 409; an invalid offset or malformed query returns 400; a memory failure can return 503. Read metadata again after 409 rather than joining rows from different captures.
POST /api/diagnostics/trace changes the diagnostic capture only. It requires X-Tyson-Request: 1 and a form-encoded body of at most 2048 bytes:
| Form field | Value |
|---|---|
action |
capture to trigger the current recording, or rearm to begin recording again |
capture |
Current capture ID, in the same integer range as GET |
boot |
Exact current trace boot_id |
Use Content-Type: application/x-www-form-urlencoded. Success returns trace metadata, not full controller state. Stale boot/capture or an unavailable operation returns 409. Rearming discards the current captured history, so download the required pages first. The full state also exposes mqtt_trace_status, mqtt_trace_capture, mqtt_trace_events, and mqtt_trace_reason for monitoring.
Verified against main/main.cpp (state_json, append_hotend_json, and HTTP handlers), main/print_monitor.cpp, main/receive_trace.cpp, main/wifi_scan_options.h, and discovery app/src/discovery.py. Recheck these implementations when supporting a different firmware release.