Skip to content

API Reference

The authoritative, machine-readable contract is openapi.yaml. Read it rendered in the full reference or load it into any OpenAPI viewer. This page is a human-friendly index grouped by capability.

All endpoints except GET / and POST /api/connect require a Bearer token (see Authentication). Base URL: http://localhost:19520 (LightBurn) or 19521 (MillMage) — these ports are fixed per product. Replace localhost with the host's address when the application has network access enabled; POST /api/connect remains localhost-only regardless.

With more than one copy of the same product running, the port is served by the most recently focused window — see Overview.

Pairing & service (no auth)

Method & path Description
GET / Placeholder HTML page
POST /api/connect Pair a localhost application; returns a shared secret

state capability — read-only machine & job state

Method & path Description
GET /api/status Connection status and device capabilities
GET /api/position Machine and workpiece coordinates
GET /api/jog/settings Current jog distance/speed settings
GET /api/goto/saved Saved positions from the device profile
GET /api/units Current display units
GET /api/events Server-Sent Events stream of state changes
GET /api/events/poll Latest state snapshot (polling fallback)

project capability — read-only project data

Method & path Description
GET /api/project Project metadata: filename, units, workspace, device (including laser source capabilities)
GET /api/layers All 32 layer definitions
GET /api/cuts Cut settings for all layers or operations (product-specific)
GET /api/cuts/{index} A single cut entry by flat index
GET /api/material-library Loaded material / operations library
GET /api/overlay Camera overlay image as PNG (LightBurn only)
GET /api/overlay/metadata Overlay size, workspace placement and change token

upload capability — submit files

Method & path Description
POST /api/file/upload Import a file into the current project (supports placement headers)
POST /api/file/open Replace the current project with the uploaded file

See Uploading files for the request body, X-Filename, and the placement headers (X-Position-X/Y, X-Origin, X-Group-Shapes).

project_write capability — change project data

Method & path Description
POST /api/cuts/{index} Partial update of one cut entry; returns the updated entry

See Cut settings for the fields each product accepts, how changes are validated and applied, and the 409 returned while the cut editor is open.

Conventions

  • Auth: Authorization: Bearer <token>. Every authorization failure returns a 401 with an undifferentiated {"error":"Unauthorized"} body — missing, invalid, expired, revoked, or outside your granted capability. The reasons are deliberately indistinguishable; don't branch on them. A request from off-machine while network access is off is a different case: nothing is listening on that address, so the connection is refused at the TCP layer rather than answered with a 401 — see Authentication.
  • Units: positions (/api/position, the position event) and the /api/project workspace are always in mm. LightBurn cut values, the MillMage tool block, and jog settings use the user's display units — read those from GET /api/units, which reports the speed unit as units and the distance unit as distance_units. The two are independent: the application supports modes that pair inch distances with metric speeds, so never infer one from the other. MillMage operation settings are always mm and mm/s. (/api/project units.distance is a translated UI label for display only — don't parse it.)
  • Connection state: connected (on GET /api/status and the connection event) means a device link is open right now. Ruida and some other non-GCode controllers connect just in time and disconnect once the transfer finishes, so connected is false at rest for a perfectly healthy laser and toggles around activity. It is not a presence or health check — don't show it as a fault. How long a link is held open varies by controller and by how the device is connected.
  • Job state on Ruida: job.state is always idle and progress is never present — the controller runs a job on its own once the file is sent, and the application doesn't track it. On Ruida, idle doesn't mean the machine is free. device.laser.controller on GET /api/project tells you which controller is attached.
  • Optional fields: a field the application can't report is omitted rather than zeroed — for example an axis the controller doesn't report, or job progress while idle or on controllers that don't report it. Treat absence as "unknown".
  • Async import: uploads return 202 Accepted immediately; the result arrives later as a file_imported event on GET /api/events. For /api/file/open its success is reliable only for native project files; other formats report false even when they opened — see Uploading.
  • Events: the SSE stream opens with a :connected comment. "On change" events aren't replayed to a new subscriber, so call GET /api/events/poll once after connecting to get the current state.
  • Partial updates: POST /api/cuts/{index} changes only the fields in the body and validates the whole body first — on any error nothing changes.
  • Product differences: GET /api/cuts, POST /api/cuts/{index} and GET /api/material-library use product-specific shapes — dispatch on the product field. The overlay endpoints are LightBurn-only and return 404 on MillMage.
  • Machine differences: LightBurn cut entries carry a profile of galvo, gantry or unknown. On kind: "normal" entries that comes with exactly one matching params.galvo / params.gantry block; kind: "image" entries carry the profile but no block. Switching the active device changes the shape for an unchanged project, so don't cache it — see Cut settings.
  • Binary responses: GET /api/overlay returns image/png on success but application/json on error — branch on the status code, not the content type.

Stability

Endpoint and field details may change between application releases until versioning ships — see Versioning. Only documented behaviour is supported; undocumented responses may change without notice.