Send a file with multipart/form-data to OctoPrint REST API

which contrib multipart node are you using?

that is node-red-contrib-http-request-multipart but no luck so far
The standard request node I have problem with setting boundary (see above)

(@dceejay - please correct me if I am wrong)
I believe the issue is that the file-in node is trying to read the file from the machine running NR not the machine the browser is running on. i.e. if NR is running on a Pi and you are using a browser on a Windows machine, the file-in node will look for the file on the Pi.

You may need to use an ftp node to move the file from the Windows box to the Pi THEN snd it to OctoPrint.

No zenofmud the file get correctly uploaded on the Pi see above (beside I don't use Windows nor mac machine since 15 years)
The real issue is that multipart form-data is not officially supported neither for http-in node nor for http-request.
I've discovered that is in the plan to officially support it since 2 years.

For http-in the contrib node-red-contrib-http-multipart does a perfect job and receive the file and it pass the name along in the flow.
For posting it to octopi I should use http-request but since is not supported I've tried with node-red-contrib-http-request-multipart but I give up because I'm not sure is working correctly, the node give me an error: "TypeError: source.on is not a function"

I'm now trying with node-red-contrib-send-multipart that throw "TypeError: Cannot read property 'statusCode' of undefined"

If anyone want to try out below is the flow to request the App token then the key from OctoPrint it get saved in a global.OctoPi_key and used in the subflow OctoPi API Key to set the headers for all the REST request.

[{"id":"1fa881f4.6e95d6","type":"subflow","name":"OctoPi API Key","info":"","category":"","in":[{"x":140,"y":120,"wires":[{"id":"de8fbdd1.b04078"}]}],"out":[{"x":460,"y":120,"wires":[{"id":"de8fbdd1.b04078","port":0}]}],"icon":"node-red/alert.png"},{"id":"de8fbdd1.b04078","type":"function","z":"1fa881f4.6e95d6","name":"get API key","func":"msg.headers = {};\nmsg.headers['X-Api-Key'] = global.get('OctoPi_key');\nreturn msg;\n","outputs":1,"noerr":0,"x":310,"y":120,"wires":[[]]},{"id":"fb3a7447.7eb7f8","type":"debug","z":"9b9a811f.0afc7","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":830,"y":1160,"wires":[]},{"id":"6521fca8.7e41dc","type":"http request","z":"9b9a811f.0afc7","name":"request token","method":"POST","ret":"txt","url":"192.168.1.5/plugin/appkeys/request","tls":"","x":360,"y":1020,"wires":[["36d5558.7d387aa"]]},{"id":"9e0eb1b9.a18038","type":"inject","z":"9b9a811f.0afc7","name":"request API token","topic":"","payload":"{\"app\":\"red-node\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":1020,"wires":[["6521fca8.7e41dc"]]},{"id":"e250eb1f.7bbff8","type":"http request","z":"9b9a811f.0afc7","name":"return API key","method":"GET","ret":"txt","url":"","tls":"","x":540,"y":1100,"wires":[["fb3a7447.7eb7f8","fe246931.9c88b"]]},{"id":"3518b385.3fe54c","type":"inject","z":"9b9a811f.0afc7","name":"Poll Key","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":120,"y":1120,"wires":[[]]},{"id":"b3d8dbd4.544fc","type":"http request","z":"9b9a811f.0afc7","name":"Fetch list of existing application keys","method":"GET","ret":"txt","url":"192.168.1.5/api/plugin/appkeys","tls":"","x":530,"y":1220,"wires":[["fb3a7447.7eb7f8"]]},{"id":"70e549a5.1a2b38","type":"inject","z":"9b9a811f.0afc7","name":"List All","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":1220,"wires":[["7b5891f1.cbe95"]]},{"id":"8298d6fc.da7bd","type":"comment","z":"9b9a811f.0afc7","name":"Generate API-KEY for OctoPrint","info":"","x":170,"y":940,"wires":[]},{"id":"c2be721c.dd9248","type":"comment","z":"9b9a811f.0afc7","name":"#1 request API token","info":"","x":140,"y":980,"wires":[]},{"id":"e67f38d3.df7bd","type":"comment","z":"9b9a811f.0afc7","name":"#2 Poll for decision on existing request","info":"","x":190,"y":1080,"wires":[]},{"id":"8afd6236.6d1a18","type":"function","z":"9b9a811f.0afc7","name":"set API key","func":"global.set('OctoPi_key', msg.payload.api_key)\nreturn msg;","outputs":1,"noerr":0,"x":910,"y":1100,"wires":[[]]},{"id":"36d5558.7d387aa","type":"json","z":"9b9a811f.0afc7","name":"","property":"payload","action":"","pretty":false,"x":570,"y":1020,"wires":[["1b521831.8d621"]]},{"id":"b261ee25.672178","type":"debug","z":"9b9a811f.0afc7","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":990,"y":940,"wires":[]},{"id":"1b521831.8d621","type":"function","z":"9b9a811f.0afc7","name":"set poll url","func":"msg.url = null\nmsg.url = \"192.168.1.5/plugin/appkeys/request/\"+msg.payload.app_token\nreturn msg;","outputs":1,"noerr":0,"x":730,"y":1020,"wires":[["b261ee25.672178","ccf34dd8.f8cd9"]]},{"id":"ccf34dd8.f8cd9","type":"delay","z":"9b9a811f.0afc7","name":"wait 5 sec confirm in OctoPrint UI","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":1000,"y":1020,"wires":[["e250eb1f.7bbff8"]]},{"id":"fe246931.9c88b","type":"json","z":"9b9a811f.0afc7","name":"","property":"payload","action":"","pretty":false,"x":770,"y":1100,"wires":[["8afd6236.6d1a18"]]},{"id":"e6836b36.94781","type":"comment","z":"9b9a811f.0afc7","name":"#3 List All allowed apps","info":"Verify that the API key is working listing all the allowed apps","x":140,"y":1160,"wires":[]},{"id":"7b5891f1.cbe95","type":"subflow:1fa881f4.6e95d6","z":"9b9a811f.0afc7","name":"","x":260,"y":1220,"wires":[["b3d8dbd4.544fc","14086af5.0b7a45"]]},{"id":"14086af5.0b7a45","type":"debug","z":"9b9a811f.0afc7","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":830,"y":1240,"wires":[]},{"id":"d8c75790.7e53","type":"debug","z":"9b9a811f.0afc7","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":590,"y":1160,"wires":[]},{"id":"54bb9a91.90cc44","type":"inject","z":"9b9a811f.0afc7","name":"","topic":"","payload":"OctoPi_key","payloadType":"global","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":370,"y":1160,"wires":[["d8c75790.7e53"]]}]

And here is how I've tried to use node-red-contrib-send-multipart

[{"id":"1fa881f4.6e95d6","type":"subflow","name":"OctoPi API Key","info":"","category":"","in":[{"x":140,"y":120,"wires":[{"id":"de8fbdd1.b04078"}]}],"out":[{"x":460,"y":120,"wires":[{"id":"de8fbdd1.b04078","port":0}]}],"icon":"node-red/alert.png"},{"id":"de8fbdd1.b04078","type":"function","z":"1fa881f4.6e95d6","name":"get API key","func":"msg.headers = {};\nmsg.headers['X-Api-Key'] = global.get('OctoPi_key');\nreturn msg;\n","outputs":1,"noerr":0,"x":310,"y":120,"wires":[[]]},{"id":"2701ee04.a0bf52","type":"httpInMultipart","z":"9b9a811f.0afc7","name":"","url":"/octopi-upload","method":"post","fields":"[{\"name\": \"file\", \"maxCount\": 1}, {\"filename\":\"\"}]","swaggerDoc":"","x":130,"y":2780,"wires":[["d601341.b493c48"]]},{"id":"d601341.b493c48","type":"change","z":"9b9a811f.0afc7","name":"set payload","rules":[{"t":"set","p":"filename","pt":"msg","to":"req.files['file'][0].path","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":2780,"wires":[["131edc7f.8c3e4c","665a2eec.c1dd7"]]},{"id":"131edc7f.8c3e4c","type":"debug","z":"9b9a811f.0afc7","name":"","active":true,"tosidebar":true,"console":false,"complete":"req.files","x":550,"y":2700,"wires":[]},{"id":"665a2eec.c1dd7","type":"file in","z":"9b9a811f.0afc7","name":"","filename":"","format":"","chunk":false,"sendError":false,"x":310,"y":2840,"wires":[["377c8d6c.890e42"]]},{"id":"377c8d6c.890e42","type":"base64","z":"9b9a811f.0afc7","name":"","action":"","property":"payload","x":340,"y":2900,"wires":[["c84a8565.424c8"]]},{"id":"c84a8565.424c8","type":"subflow:1fa881f4.6e95d6","z":"9b9a811f.0afc7","name":"","x":280,"y":3000,"wires":[["f75343ff.ea4bf"]]},{"id":"f75343ff.ea4bf","type":"function","z":"9b9a811f.0afc7","name":"Set Headers","func":"var file = msg.req.files['file'][0];\n\n//msg.headers = {};\n//msg.headers['Accept'] = 'application/json';\nmsg.headers['Content-Type'] = 'multipart/form-data';\n\nmsg.headers['Content-Disposition'] = `form-data; name=\"file\"; filename=\"${file.originalname}\";`\n\nmsg.filepath = file\n/*\nvar buffer = msg.payload;\nmsg.form={}\nmsg.payload={\n    \"file\" : [buffer]\n    };\n//\"${buffer.toString('base64')}\"\n//\"data:application/octet-stream;base64,${buffer.toString('base64')}\"\n*/\n\n\nreturn msg;","outputs":1,"noerr":0,"x":570,"y":2980,"wires":[["a2efc04a.6800e"]]},{"id":"a2efc04a.6800e","type":"http-send-multipart","z":"9b9a811f.0afc7","name":"","ret":"txt","filepath":"","url":"192.168.1.5/api/files/local","tls":"","x":790,"y":2980,"wires":[["153617db.4665a8"]]},{"id":"153617db.4665a8","type":"http response","z":"9b9a811f.0afc7","name":"response","statusCode":"200","headers":{},"x":1080,"y":2980,"wires":[]},{"id":"67ee6af2.24d00c","type":"ui_template","z":"9b9a811f.0afc7","group":"e97f66fe.f451c","name":"upload Template","order":2,"width":0,"height":0,"format":"\n    <form id=\"fileUpload\" action=\"/octopi-upload\" method=\"post\" enctype=\"multipart/form-data\">\n    <input type=\"file\" name=\"file\" id=\"fileToUpload\">\n    <input type=\"submit\" value=\"upload gcode\" name=\"submit\" \n        style=\"background-color: chartreuse;\"\n        class=\"form-button\">\n    </form>\n    ","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":130,"y":2700,"wires":[[]]},{"id":"fa161a5c.6024f8","type":"comment","z":"9b9a811f.0afc7","name":"Upload a file in octopi via the REST API","info":"","x":190,"y":2640,"wires":[]},{"id":"e97f66fe.f451c","type":"ui_group","z":"","name":"Files","tab":"81b4005b.665ae8","order":5,"disp":true,"width":"6","collapse":true},{"id":"81b4005b.665ae8","type":"ui_tab","z":"9b9a811f.0afc7","name":"3DPrint","icon":"camera_alt","order":2,"disabled":false,"hidden":false}]

Two issues I see

  1. in httpSendMultipart it says:
Provides a node for posting multipart form-data.

The URL must be configured in the node.

url, if set, is used as the url of the request. Must start with http: or https:

yours doesn't
2) in your 'Set Headers' function you are setting

var file = msg.req.files['file'][0];
.
.
.
msg.filepath = file

I suggest you add a debug node (set to display complete msg object) to the output of the 'OctoPi API Key' node and see what you are getting for the file path

That isn't 100% true. The HTTP In node does support file uploads now - select Post and tick the box to enable file uploads.

To send a multipart upload from the HTTP Request node is more work, but doable.

Here's a Function that will construct a suitable message to pass to the HTTP Request node:

msg.headers = {
    "Content-Type": "multipart/form-data; boundary=------------------------d74496d66958873e"
}


msg.payload = '--------------------------d74496d66958873e\r\n'+
'Content-Disposition: form-data; name="select"\r\n'+
'\r\n'+
'true\r\n'+
'--------------------------d74496d66958873e\r\n'+
'Content-Disposition: form-data; name="print"\r\n'+
'\r\n'+
'true\r\n'+
'--------------------------d74496d66958873e\r\n'+
'Content-Disposition: form-data; name="file"; filename="whistle_v2.gcode"\r\n'+
'Content-Type: application/octet-stream\r\n'+
'\r\n'+
'contents of the file\r\n'+
'--------------------------d74496d66958873e--\r\n';


return msg;

I've based the content from the example request you shared earlier.

A couple key points

  • you need to use \r\n for newlines
  • the boundary string set in the header defines how the separate form parts are separated in the body. When it appears in the body, it has two extra -- in front. The very last one also has -- added to the end. (Took quite a bit of googling and examing curl trace to spot that)

Hopefully from that you can insert the contents of the file you want to upload at the appropriate point.

Thanks knolleary I didn't know I can just compose the payload this way!
But how do I put the contents of the file.
In my flow, as you can see, I've used the file-in node that send it as "Buffer object" then base64 node then got inserted as ${buffer.toString('base64')} is this correct?

I've added a more complete example to the flow library here: https://flows.nodered.org/flow/cbf44e064b9406a1175a7e8d589f66ac

Let me know if that helps or if anything needs clarifying there.

IT WORKED! I was indeed able to send it adapting your example! Goog job!

How did you end up adding the contents of the file. I am getting "Error: write EPIPE" when I send this via POST, which AFAIK means that octoprint is dropping the connection before the information is uploaded because it is not in the format octoprint is expecting

`msg.headers = {
    "Content-Type": "multipart/form-data; boundary=------------------------d74496d66958873e",
    'Content-Length': "Buffer.byteLength(data)"
}

msg.payload = '--------------------------d74496d66958873e\r\n'+
'Content-Disposition: form-data; name="select"\r\n'+
'\r\n'+
'true\r\n'+
'--------------------------d74496d66958873e\r\n'+
'Content-Disposition: form-data; name="print"\r\n'+
'\r\n'+
'true\r\n'+
'--------------------------d74496d66958873e\r\n'+
'Content-Disposition: form-data; name="file"; filename="'+msg.filename+'"\r\n'+
'Content-Type: application/octet-stream\r\n'+
'\r\n'+
msg.payload+'\r\n'+
'--------------------------d74496d66958873e--\r\n';


return msg;`

and this is how the nodes are set up

[{"id":"3f7e7ffa.4734","type":"ui_template","z":"d0969e5.ee64a6","group":"bc476fb4.91eae","name":"Upload HTML","order":13,"width":"8","height":"2","format":"<h1>Upload</h1>\n\n<form action=\"/temp\" method=\"POST\" enctype=\"multipart/form-data\">\n    <input type=\"file\" name=\"myFile\" />\n    <input type=\"submit\" value=\"Submit\">\n</form>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":1300,"y":280,"wires":[[]]},{"id":"1e25fc5f.8406f4","type":"change","z":"d0969e5.ee64a6","name":"AA File Upload","rules":[{"t":"set","p":"url","pt":"msg","to":"racka/aa/api/files/local?apikey=","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":2160,"y":280,"wires":[["396f0b7f.485e44"]]},{"id":"396f0b7f.485e44","type":"http request","z":"d0969e5.ee64a6","name":"","method":"POST","ret":"txt","paytoqs":false,"url":"","tls":"","proxy":"","authType":"basic","x":2350,"y":280,"wires":[[]]},{"id":"ae8c13a7.6bfe1","type":"function","z":"d0969e5.ee64a6","name":"Format the header and payload","func":"msg.headers = {\n    \"Content-Type\": \"multipart/form-data; boundary=------------------------d74496d66958873e\",\n    'Content-Length': \"Buffer.byteLength(data)\"\n}\n\n\nmsg.payload = '--------------------------d74496d66958873e\\r\\n'+\n'Content-Disposition: form-data; name=\"select\"\\r\\n'+\n'\\r\\n'+\n'true\\r\\n'+\n'--------------------------d74496d66958873e\\r\\n'+\n'Content-Disposition: form-data; name=\"print\"\\r\\n'+\n'\\r\\n'+\n'true\\r\\n'+\n'--------------------------d74496d66958873e\\r\\n'+\n'Content-Disposition: form-data; name=\"file\"; filename=\"'+msg.filename+'\"\\r\\n'+\n'Content-Type: application/octet-stream\\r\\n'+\n'\\r\\n'+\nmsg.payload+'\\r\\n'+\n'--------------------------d74496d66958873e--\\r\\n';\n\n\nreturn msg;","outputs":1,"noerr":0,"x":1810,"y":360,"wires":[["1e25fc5f.8406f4","b8ce260.d4f45d8"]]},{"id":"c000a6a1.93de68","type":"http in","z":"d0969e5.ee64a6","name":"","url":"/temp","method":"post","upload":true,"swaggerDoc":"","x":1430,"y":360,"wires":[["feb4aac.a06d158"]]},{"id":"b8ce260.d4f45d8","type":"debug","z":"d0969e5.ee64a6","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":2290,"y":400,"wires":[]},{"id":"feb4aac.a06d158","type":"function","z":"d0969e5.ee64a6","name":"Gcode","func":"var msg = {payload: msg.req.files[0].buffer.toString(), filename: msg.req.files[0].originalname}\nreturn msg;","outputs":1,"noerr":0,"x":1590,"y":360,"wires":[["ae8c13a7.6bfe1"]]},{"id":"bc476fb4.91eae","type":"ui_group","z":"","name":"AA","tab":"86ac15ae.8d8e88","order":1,"disp":true,"width":"10","collapse":true},{"id":"86ac15ae.8d8e88","type":"ui_tab","z":"","name":"Overview","icon":"dashboard","disabled":false,"hidden":false}]

Open your gcode function,

  1. do you see any thing like a yellow triangle?

  2. What do you see when you hover over it?

  3. What do you think that means?

  4. have you read https://nodered.org/docs/user-guide/messages ?

  5. do you understand it?

  6. Put a debug node - set to display 'complete msg object' - on your http request what does it show?

my msg object for the final http post request :

payload:
--------------------------d74496d66958873e
Content-Disposition: form-data; name="select"

true
--------------------------d74496d66958873e
Content-Disposition: form-data; name="print"

true
--------------------------d74496d66958873e
Content-Disposition: form-data; name="file"; filename="a_gcode.gcode"
Content-Type: application/octet-stream; 
my gcode
--------------------------d74496d66958873e--
_msgid: "XXXXXXXXX"
headers:
Content-Type: "multipart/form-data; boundary=------------------------d74496d66958873e"

my msg object after the request shown in the debug is:

payload: 
"Error: socket hang up : http://racka/aa/api/files/local?apikey=XXXXXXXXXXX

_msgid: same as before
headers: same as before

statusCode: "ECONNRESET"

What are the answers to the other questions?

  1. do you see any thing like a yellow triangle?
    Yes
  2. What do you see when you hover over it?
    the yellow triangle said:
    no semi - colon: fixed
    msg already defined: changed name of returned variable to gcode
  3. What do you think that means?
  4. have you read https://nodered.org/docs/user-guide/messages ?
    Yes
  5. do you understand it?
    For the most part

Sometimes the error is "Error: write EPIPE" as opposed to socket hang up

Great you fixed that part! Now you have an error
"Error: socket hang up : http://racka/aa/api/files/local?apikey=XXXXXXXXXXX
what happens if you try that URL in a browser window?

That's good to hear, thank you for your help. Trying that url gives me

{
  "files": [], 
  "free": 58407620608, 
  "total": 62811418624
}

I have managed to get a file upload to work using a test gcode, putting "M117" in the gcode section.

In the multipart/form-data I have the gcode buffer converted to to string:

msg.req.files[0].buffer.toString()

where msg.req.files[0].buffer is:

[59,32,103,101,110,101,114,97,116,101,100,32,98,121,32,83,108,105,99,51,114,32,80,114,117,115,97,32,69,100,105,116,105,111,110,32,49,46,52,49,46,51,43,32,111,110,32,50,48,49,57,45,48,52,45,48,56,32,97,116,32,49,50,58,50,54,58,49,52,10,10,59,32,10,10,59,32,101,120,116,101,114,110,97,108,32,112,101,114,105,109,101,116,101,114,115,32,101,120,116,114,117,115,105,111,110,32,119,105,100,116,104,32,61,32,48,46,52,53,109,109,10,59,32,112,101,114,105,109,101,116,101,114,115,32,101,120,116,114,117,115,105,111,110,32,119,105,100,116,104,32,61,32,48,46,52,53,109,109,10,59,32,105,110,102,105,108,108,32,101,120,116,114,117,115,105,111,110,32,119,105,100,116,104,32,61,32,48,46,52,53,109,109,10,59,32,115,111,108,105,100,32,105,110,102,105,108,108,32,101,120,116,114,117,115,105,111,110,32,119,105,100,116,104,32,61,32,48,46,52,53,109,109,10,59,32,116,111,112,32,105,110,102,105,108,108,32,101,120,116,114,117,115,105,111,110,32,119,105,100,116,104,32,61,32,48,46,52,48,109,109,10,59,32,102,105,114,115,116,32,108,97,121,101,114,32,101,120,116,114,117,115,105,111,110,32,119,105,100,116,104,32,61,32,48,46,52,50,109,109,10,10,77,55,51,32,80,48,32,82,57,57,10,77,55,51,32,81,48,32,83,49,48,48,10,77,50,48,49,32,88,49,48,48,48,32,89,49,48,48,48,32,90,49,48,48,48,32,69,53,48,48,48,32,59,32,115,101,116,115,32,109,97,120,105,109,117,109,32,97,99,99,101,108,101,114,97,116,105,111,110,115,44,32,109,109,47,115,101,99,94,50,10,77,50,48,51,32,88,50,48,48,32,89,50,48,48,32,90,49,50,32,69,49,50,48,32,59,32,115,101,116,115,32,109,97,120,105,109,117,109,32,102,101,101,100,114,97,116,101,115,44,32,109,109,47,115,101,99,10,77,50,48,52,32,80,49,50,53,48,32,82,49,50,53,48,32,84,49,50,53,48,32,59,32,115,101,116,115,32,97,99,99,101,108,101,114,97,116,105,111,110,32,40,80,44,32,84,41,32,97,110,100,32,114,101,116,114,97,99,116,32,97,99,99,101,108,101,114,97,116,105,111,110,32,40,82,41,44,32,109,109,47,115,101,99,94,50,10,77,50,48,53,32,88,56,46,48,48,32,89,56,46,48,48,32,90,48,46,52,48,32,69,49,46,53,48,32,59,32,115,101,116,115,32,116,104,101,32,106,101,114,107,32,108,105,109,105,116,115,44,32,109,109,47,115,101,99,10,77,50,48,53,32,83,48,32,84,48,32,59,32,115,101,116,115,32,116,104,101,32,109,105,110,105,109,117,109,32,101,120,116,114,117,100,105,110,103,32,97,110,100,32,116,114,97,118,101,108,32,102,101,101,100,32,114,97,116,101,44,32,109,109,47,115,101,99,10,77,49,48,55,10,77,49,49,53,32,85,51,46,53,46,49,32,59,32,116,101,108,108,32,112,114,105,110,116,101,114,32,108,97,116,101,115,116,32,102,119,32,118,101,114,115,105,111,110,10,77,56,51,32,32,59,32,101,120,116,114,117,100,101,114,32,114,101,108,97,116,105,118,101,32,109,111,100,101,10,77,49,48,52,32,83,50,49,53,32,59,32,115,101,116,32,101,120,116,114,117,100,101,114,32,116,101,109,112,10,77,49,52,48,32,83,54,48,32,59,32,115,101,116,32,98,101,100,32,116,101,109,112,10,77,49,57,48,32,83,54,48,32,59,32,119,97,105,116,32,102,111,114,32,98,101,100,32,116,101,109,112,10,77,49,48,57,32,83,50,49,53,32,59,32,119,97,105,116,32,102,111,114,32,101,120,116,114,117,100,101,114,32,116,101,109,112,10,71,50,56,32,87,32,59,32,104,111,109,101,32,97,108,108,32,119,105,116,104,111,117,116,32,109,101,115,104,32,98,101,100,32,108,101,118,101,108,10,71,56,48,32,59,32,109,101,115,104,32,98,101,100,32,108,101,118,101,108,105,110,103,10,71,49,32,89,45,51,46,48,32,70,49,48,48,48,46,48,32,59,32,103,111,32,111,117,116,115,105,100,101,32,112,114,105,110,116,32,97,114,101,97,10,71,57,50,32,69,48,46,48,10,71,49,32,88,54,48,46,48,32,69]

Converting it to a string gives me a string which appears readable but evidently not so to octoprint

Just as a side question, why not use the Octoprint built in file upload? I do it all the time to upload files from my Mac to the RPi running Octoprint.

I am trying to view multiple instances of Octoprint on a single dashboard, so it would be useful to be able to upload files without going to the main page of each instance. I'm going to shelve this one for now I think, thank you for your help though.

@knolleary: Has there been any development on the HTTP-request node, to support multipart/upload of a file-buffer (or base64 encoded) (e.g .pdf) file along with some JSON out of the box?

Or is it still required to build the request by hand in a function node like in the old example from 2019?

@jmk-randers the Request node has had file upload support in it since v0.20 released in March 2019. The sidebar help for the node describes how to do it.