The upload capability exposes two endpoints. Both take the
file as the raw request body (application/octet-stream) with the filename
in an X-Filename header, and return 202 Accepted immediately — the import
runs asynchronously.
| Endpoint | Behavior |
|---|---|
POST /api/file/upload |
Imports the file into the current project (merge). Supports placement headers. |
POST /api/file/open |
Replaces the current project with the file (File → Open). No placement headers. |
import urllib.request
data = open("design.svg", "rb").read()
req = urllib.request.Request(
"http://localhost:19520/api/file/upload",
data=data,
method="POST",
headers={
"Authorization": f"Bearer {bearer_token(secret)}",
"Content-Type": "application/octet-stream",
"X-Filename": "design.svg",
},
)
print(urllib.request.urlopen(req).read()) # {"status": "accepted", ...}The X-Filename extension selects the loader/translator, so set it to match the
payload (.svg, .dxf, .lbrn2, …).
By default the import lands at the current view center. To anchor it at a specific workspace coordinate:
| Header | Meaning |
|---|---|
X-Position-X, X-Position-Y |
Anchor point in workspace mm (workpiece origin). Must be sent as a pair. |
X-Origin |
Which corner of the import's bounding box sits at the anchor: top-left … bottom-right, or center (default). Ignored unless both position headers are present. |
X-Group-Shapes |
"true" keeps imported shapes grouped; "false" (default) uses the app's global grouping preference. |
The 202 only means the file was received — for both endpoints. The actual
result arrives over the state channel as a
file_imported event, carrying {"success": true} or
{"success": false, "error": "…"}. Request the state capability too if you
need to confirm the import.
For /api/file/open, success is reliable only for native project files
(.lbrn / .lbrn2 on LightBurn, .mage on MillMage). Opening a template
(.lbt, .mmt) or any other format — SVG, DXF, images — reports
"success": false even when the file opened correctly. For those formats,
confirm the open with GET /api/project after the event instead: a successful
open leaves filename empty and shape_count counts the opened shapes. The
error text is translated for display, so don't match on it.
/api/file/upload reports accurately for every format.
This event is only delivered on the GET /api/events SSE stream. The
GET /api/events/poll fallback returns a snapshot of current state — position,
job, overrides, aux, connection, settings — and does not carry events, so a
polling client cannot observe file_imported. If you need import confirmation,
hold an SSE connection open across the upload. Open the stream before
sending the file, so the event can't arrive before you're listening.
See openapi.yaml for the full header and response schemas.