This page documents the Bambu firmware 3.10.4 implementation. It does not establish compatibility with Prusa firmware. See Developer reference for the common host, origin, and request-header rules, and State and discovery for state fields.
Download examples are read-only. Uploads below are protocol descriptions for an explicit maintenance operation: calibration uploads replace stored calibration, and firmware installation interrupts heater control and restarts the HAT. They should not run automatically when an integration starts.
Each path accepts both GET to download and POST to restore:
| Path | JSON kind |
Contents |
|---|---|---|
/api/calibration/hotend-mapping |
hotend-mapping |
Hotend electrical mapping and maximum setting |
/api/calibration/hotend-presets |
hotend-presets |
The five preset results and their matching mapping reference |
/api/calibration/bed |
bed |
Bed mapping and maximum setting |
Select the hotend with the X-Tyson-Hotend header, not a query parameter. Its values are left or right; an omitted header selects left. right requires an X2D configuration and is invalid for bed transfers. Use left or omit this header for bed files. Unknown header values return 400; an unavailable right channel returns 409.
Replace the documentation IP with the HAT's address:
curl --fail --show-error --max-time 15 \
-H "Accept: application/json" \
-H "X-Tyson-Hotend: left" \
--output tyson-hotend-mapping.json \
http://192.0.2.42/api/calibration/hotend-mapping
Successful downloads return 200, Content-Type: application/json, Cache-Control: no-store, and an attachment filename in Content-Disposition. X2D hotend filenames include -left or -right; the bed filename is tyson-bed-mapping.json.
A configured printer serial and saved, compatible calibration must exist. Transfers are rejected during factory reset, the relevant calibration, or pending calibration saving. Downloads do not require disabling otherwise-running mapping, but a preset bank must match the current hotend mapping.
Keep the downloaded JSON unchanged when storing a backup. The format is explicitly versioned:
| Field | Meaning |
|---|---|
format |
Exactly tyson-calibration |
version |
1 for unlabelled files, or 2 for hotend-channel-labelled files |
kind |
One of the kinds in the endpoint table |
printer_serial |
Must match the HAT's currently configured printer serial |
hotend |
Required in version 2; exactly left or right matching the selected channel |
data |
Mapping or preset contents |
Version 1 uses exactly format, version, kind, printer_serial, and data at the top level. Version 2 adds hotend. Bed backups use version 1. Right-hotend uploads require version 2; version 1 is accepted only for left-hotend or bed transfers. X2D hotend downloads are version 2.
Mapping data contains maximum and points. Each point is [DAC code, centigrade]; centigrade is an integer in hundredths of a degree Celsius in these files. Preset data contains mapping_points and five presets with results, measurements, and correction values. This interchange format is not a raw memory/NVS dump.
The decoder checks the exact key set, rejects duplicate or unknown keys, validates numeric ranges and mapping constraints, checks the printer serial and channel, and rejects a running preset result. It accepts at most 16384 bytes, limits JSON nesting to eight levels, rejects embedded NULs, and rejects trailing non-whitespace data. Do not add application metadata inside the backup JSON; store it separately.
Send the downloaded JSON as the raw request body, not a multipart upload or form field. Use these headers:
POST /api/calibration/hotend-mapping HTTP/1.1
Host: 192.0.2.42
Accept: application/json
Content-Type: application/json
X-Tyson-Request: 1
X-Tyson-Hotend: left
Content-Length: <actual JSON byte count>
<unchanged downloaded calibration JSON>
This is a request template, not a file to upload as written. The calibration handler parses the body as JSON; Content-Type: application/json matches the dashboard. It does not require X-Tyson-Boot, so the client should track the current boot_id, printer serial, and selected channel and verify them around a transfer.
Before a bed upload, bed control and its remembered/restoring enable state must be off, with no active bed heater or fresh nonzero Bambu bed target. Before a hotend upload, mapping must release the DAC, complete nozzle-off/stop confirmation, and clear its remembered/restoring enable state; a fresh nonzero nozzle target blocks the upload. Pending or failed saving of the disabled preference also blocks restoration. The server checks these conditions again before writing.
Restoring a changed hotend mapping clears incompatible preset results. Restore the matching mapping first, then its presets. Preset restoration requires an exact match with the current mapping table. Uploading a backup does not enable heating or mapping.
On success the handler returns a full controller state snapshot, not an accepted flag. Calibration has been saved to flash and applied in memory. Verify boot_id, the selected channel, calibration flags, and any remaining persistence/fault status before a separate enable operation. If a connection times out or changes, reread state and, if necessary, download the stored backup before deciding to retry.
| HTTP status | Calibration meaning |
|---|---|
200 |
Backup downloaded, or upload saved and full state returned |
400 |
Invalid channel, malformed/incompatible JSON, wrong printer/channel/type, or incomplete body |
403 |
Common host/origin/request-header or firmware-maintenance policy rejection |
409 |
Transfer guard, missing compatible calibration, or state changed before applying |
413 |
Empty body or body larger than 16384 bytes |
500 |
Calibration could not be saved to flash |
503 |
Insufficient memory for transfer or state serialization |
Calibration handler errors are normally text/plain; common policy errors and state serialization errors can be JSON. Check HTTP status and response content type rather than assuming every response is JSON. See the Calibration and backups guide for the user workflow.
POST /api/firmware accepts a raw application .bin, not ZIP, ELF, a merged flash image, or multipart data. Read state immediately before preparing an update:
| State field | Meaning |
|---|---|
firmware_update_supported |
Current running partition layout supports this web update path |
firmware_update_max_bytes |
Maximum accepted application size for the available OTA slot |
firmware_update_busy |
An update is active |
firmware_update_restarting |
Image accepted; restart is pending |
firmware_update_written, firmware_update_total |
Bytes written and expected upload size |
firmware_boot_pending |
New boot is still awaiting startup confirmation |
boot_id, firmware_version |
Boot identity and running version used to verify the result |
The request must include the following:
POST /api/firmware HTTP/1.1
Host: 192.0.2.42
Content-Type: application/octet-stream
X-Tyson-Request: 1
X-Tyson-Boot: <boot_id from the current state>
Content-Length: <actual application binary byte count>
<raw application binary bytes>
The content type is checked exactly; do not append a charset parameter. The boot header must match the current boot. There is no separate SHA-256 request header. The image must have the expected ESP32-S2 application descriptor/project identity, an appended image hash, a supported flash-size declaration, and a version accepted by the image validator (3.8.65 or newer in this firmware). Length must be at least 288 bytes and at most firmware_update_max_bytes. ESP-IDF validates the completed image before it becomes the next boot image.
The server writes the inactive OTA slot. It waits for conflicting persistent writes or reset operations to finish before accepting maintenance. Do not interpret acceptance as proof the printer is idle: this endpoint's guard checks reset and saving conflicts, not whether a print or calibration is active. Entering maintenance inhibits bed and chamber heater outputs; the accepted update then restarts the controller and temporarily interrupts temperature mapping. Schedule it as an operator-approved maintenance action, following Firmware updates.
The receive deadline is 180 seconds; the dashboard allows 210 seconds for its HTTP request. The implementation receives a known-size body and does not provide resumable chunks or a separate cancellation endpoint. Canceling the client upload can abort reception, but a lost response does not cancel an already verified and selected image.
Successful upload returns HTTP 200 with:
{"accepted":true,"restarting":true}
This means the image passed validation and was selected for startup. It does not confirm a successful new boot. The firmware schedules restart after approximately two seconds, with output/fan shutdown handling completing before restart.
boot_id and expected version from the selected release.GET /api/state, allowing for temporary connection failure during restart.boot_id and the expected firmware_version.firmware_boot_pending becomes false. The firmware attempts startup confirmation after at least 30 seconds from its boot-ready point; confirmation can take longer if it must retry.For same-version reinstallations, still require a new boot and completed startup verification. If the upload response was lost, follow this verification flow before resending the image. A fully verified update is allowed to restart even if its HTTP response never reached the client.
| HTTP status | Firmware meaning |
|---|---|
200 |
Image accepted and restart scheduled |
400 |
Wrong content type, stale boot header, incompatible image, or failed image integrity check |
403 |
Common request policy rejection; includes a busy update or startup verification at the shared mutation gate |
408 |
Upload reception interrupted or timed out |
409 |
OTA layout unavailable, update/startup already active, or reset/saving conflict detected by the upload handler |
413 |
Body is smaller than 288 bytes or larger than the OTA slot |
500 |
Inactive-slot write or boot-partition selection failure |
503 |
Insufficient upload memory |
Firmware upload errors are generally plain text; common policy errors are JSON. USB installation is required when firmware_update_supported is false because the partition layout is missing. Use the Firmware Installer for that workflow.
Verified against main/main.cpp (calibration_transfer_handler, its guards, and firmware_upload_handler), main/calibration_backup.h, main/firmware_image.h, main/firmware_update.cpp, and the transfer clients in main/web/app.js.