Upload document via REST API

I’ve been reading the docs, comments here, and GitLab on how to upload a document.

Here is what I figured out so far:

curl --location 'http://127.0.0.1:8080/api/v4/documents/upload/' \
--header 'Authorization: Token 5f10c9406d99209a0065a5da4862b86dced18053' \
--form 'document_type_id="4"' \
--form 'file=@"/C:/Users/chris/OneDrive/Documents/Screenshot_1.png"'

I will expand on this once I fully figure out what I am doing.

Here is a NodeRed flow that:

  • Uploads document,
  • Adds document to correct cabinet,
  • Adds metadata value.

Required NodeRed palettes:

  • node-red-dashboard
  • node-red-contrib-ui-upload

Import flow:

[{"id":"f6f2187d.f17ca8","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"549c7642da358bdd","type":"http request","z":"f6f2187d.f17ca8","name":"","method":"use","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":750,"y":200,"wires":[["12e98b9c214451b9"]]},{"id":"12e98b9c214451b9","type":"function","z":"f6f2187d.f17ca8","name":"store token","func":"if(msg.statusCode === 200) {\n    node.log('obtain auth token success');\n    flow.set('mayan.auth.token', msg.payload.token);\n    flow.set('mayan.auth.valid', true);\n} else {\n    node.warn('obtain auth token failed!');\n    flow.set('mayan.auth.token', null);\n    flow.set('mayan.auth.valid', false);\n}\nreturn null;","outputs":0,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":950,"y":200,"wires":[]},{"id":"5eaa7606e06ff73a","type":"inject","z":"f6f2187d.f17ca8","name":"run at startup","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":240,"y":100,"wires":[["df6d1478d9c32f01"]]},{"id":"df6d1478d9c32f01","type":"function","z":"f6f2187d.f17ca8","name":"settings","func":"flow.set('mayan.api.url_base', 'http://mayan-app-1:8000/api/v4');\nflow.set('mayan.credential.username', 'admin');\nflow.set('mayan.credential.password', 'wmHMhj5tEa');\nreturn msg;\n\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":420,"y":100,"wires":[["e69bc0c889878581"]]},{"id":"e69bc0c889878581","type":"function","z":"f6f2187d.f17ca8","name":"build request","func":"msg.method = \"POST\";\nmsg.url = `${flow.get('mayan.api.url_base')}/auth/token/obtain/`;\nmsg.headers = {\n    'Content-Type': 'application/json',\n    'Accept': 'application/json'\n};\nmsg.payload = {\n    'username': flow.get('mayan.credential.username'),\n    'password': flow.get('mayan.credential.password')\n};\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":550,"y":200,"wires":[["549c7642da358bdd"]]},{"id":"4c73c5ff9e205c68","type":"ui_upload","z":"f6f2187d.f17ca8","group":"70994f3107e78413","title":"Upload Invoice Without Purchase Order","accept":"","name":"upload document","order":1,"width":0,"height":5,"chunk":256,"transfer":"binary","x":230,"y":340,"wires":[["26b16b339747d9c1"]]},{"id":"bc9b5396b35ad48c","type":"http request","z":"f6f2187d.f17ca8","name":"","method":"use","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":750,"y":420,"wires":[["ae20ac342ca148c9"]]},{"id":"22a3feb2cf341c76","type":"function","z":"f6f2187d.f17ca8","name":"build request","func":"msg.method = \"POST\";\nmsg.url = `${flow.get('mayan.api.url_base')}/documents/upload/`;\nmsg.headers = {\n    'Content-Type': 'multipart/form-data',\n    'Authorization': `Token ${flow.get('mayan.auth.token')}`\n}\nmsg.payload = {\n  \"document_type_id\": msg.mayan.document_type_id,\n  \"file\": {\n    \"value\": msg.mayan.document__file_buffer,\n    \"options\": {\n      \"filename\": msg.mayan.document__file_name\n    }\n  }\n};\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":550,"y":420,"wires":[["bc9b5396b35ad48c"]]},{"id":"ae20ac342ca148c9","type":"function","z":"f6f2187d.f17ca8","name":"response","func":"if (msg.statusCode > 299) {\n    node.warn('api call failed!');\n    return null;\n}\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":940,"y":420,"wires":[["651e3acca7c91317"]]},{"id":"043652f6aed56a6a","type":"function","z":"f6f2187d.f17ca8","name":"build request","func":"msg.method = \"POST\";\nmsg.url = `${flow.get('mayan.api.url_base')}/cabinets/${msg.mayan.cabinet_id}/documents/add/`;\nmsg.headers = {\n  'Content-Type': 'application/json',\n  'Accept': 'application/json',\n  'Authorization': `Token ${flow.get('mayan.auth.token')}`\n};\nmsg.payload = {\n  'document': `${msg.mayan.document_id}`\n};\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":550,"y":500,"wires":[["c9240507c578b4ca"]]},{"id":"651e3acca7c91317","type":"function","z":"f6f2187d.f17ca8","name":"mayan data","func":"msg.mayan = {\n    ... msg.mayan,\n    'cabinet_id': 9,\n    'document_id': msg.payload.id\n}\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":350,"y":500,"wires":[["043652f6aed56a6a"]]},{"id":"c9240507c578b4ca","type":"http request","z":"f6f2187d.f17ca8","name":"","method":"use","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":750,"y":500,"wires":[["a754d9038b0e8dc0"]]},{"id":"08a94d42a1c2eba0","type":"comment","z":"f6f2187d.f17ca8","name":"upload document","info":"","x":220,"y":380,"wires":[]},{"id":"0b93ae2506f22e70","type":"comment","z":"f6f2187d.f17ca8","name":"move to cabinet","info":"","x":220,"y":460,"wires":[]},{"id":"26b16b339747d9c1","type":"function","z":"f6f2187d.f17ca8","name":"mayan data","func":"msg.mayan = {\n    'document_type_id': 4,\n    'document__file_name': msg.file.name,\n    'document__file_buffer': msg.payload\n}\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":350,"y":420,"wires":[["22a3feb2cf341c76"]]},{"id":"269e7e5d177d6a9f","type":"comment","z":"f6f2187d.f17ca8","name":"add metadata","info":"","x":210,"y":540,"wires":[]},{"id":"be978c727309e89d","type":"comment","z":"f6f2187d.f17ca8","name":"obtain auth token","info":"","x":220,"y":160,"wires":[]},{"id":"a754d9038b0e8dc0","type":"function","z":"f6f2187d.f17ca8","name":"response","func":"if (msg.statusCode > 299) {\n    node.warn('api call failed!');\n    return null;\n}\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":940,"y":500,"wires":[["34fb9392b3ad3bb0"]]},{"id":"117e43e8c99afd1e","type":"function","z":"f6f2187d.f17ca8","name":"build request","func":"msg.method = \"POST\";\nmsg.url = `${flow.get('mayan.api.url_base')}/documents/${msg.mayan.document_id}/metadata/`;\nmsg.headers = {\n  'Content-Type': 'application/json',\n  'Accept': 'application/json',\n  'Authorization': `Token ${flow.get('mayan.auth.token')}`\n};\nmsg.payload = {\n  'metadata_type_id': `${msg.mayan.metadata_type_id}`,\n  'value': `${msg.mayan.metadata__value}`\n};\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":550,"y":580,"wires":[["34182535f35e04d4"]]},{"id":"34fb9392b3ad3bb0","type":"function","z":"f6f2187d.f17ca8","name":"mayan data","func":"msg.mayan = {\n    ... msg.mayan,\n    'metadata_type_id': 6,\n    'metadata__value': \"4000\"\n}\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":350,"y":580,"wires":[["117e43e8c99afd1e"]]},{"id":"34182535f35e04d4","type":"http request","z":"f6f2187d.f17ca8","name":"","method":"use","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":750,"y":580,"wires":[["1586a8f07cda7524"]]},{"id":"1586a8f07cda7524","type":"function","z":"f6f2187d.f17ca8","name":"response","func":"if (msg.statusCode > 299) {\n    node.warn('api call failed!');\n    return null;\n}\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":940,"y":580,"wires":[["3ccda02042ae74fb"]]},{"id":"3ccda02042ae74fb","type":"debug","z":"f6f2187d.f17ca8","name":"completion","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"'complete'","targetType":"jsonata","statusVal":"","statusType":"auto","x":350,"y":660,"wires":[]},{"id":"dea3a619a0e3ce8c","type":"comment","z":"f6f2187d.f17ca8","name":"http://nodered:1880/ui","info":"","x":240,"y":300,"wires":[]},{"id":"70994f3107e78413","type":"ui_group","name":"Upload File","tab":"6ab69d53c11131b3","order":1,"disp":false,"width":"12","collapse":false,"className":""},{"id":"6ab69d53c11131b3","type":"ui_tab","name":"Document Upload","icon":"dashboard","disabled":false,"hidden":false}]
2 Likes

Thanks for sharing this process. I don’t use nodered, so I can’t download your flow to review.

Can you tell me when you are adding the metadata values, do you need to pull the metadata fields first, or are you hardcoding them into your flow?

Also are you using HTTP PATCH when you do?

Thanks

I am hardcoding the Metadata Type and its value for now; using POST.

image

1 Like

That is very helpful. I’m working doing something very similar with n8n. So this is great timing for me!

Thank you

2 Likes

Interesting, first time I am learning about n8n.

I’ve been using it for a couple of years, it’s been very dependable.

Can you post the full URL with headers, parameters, and body?

When I look at the Mayan API docs, I can’t seem to get it to match what I see here.

Thanks

POST /api/v4/documents/upload/ HTTP/1.1
Host: 127.0.0.1:8080
Authorization: Token 5f10c9406d99209a0065a5da4862b86dced18053
Content-Length: 321
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="document_type_id"

4
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="/C:/Users/chris/OneDrive/Documents/Screenshot_1.png"
Content-Type: image/png

(data)
------WebKitFormBoundary7MA4YWxkTrZu0gW--

Thank you. If I could ask, could you also show me the url for when you added the metadata?

Thanks

URL: POST http://mayan-app-1:8000/api/v4/documents/72/metadata/

Headers: { "Content-Type":"application/json", "Accept":"application/json", "Authorization":"Token 5f10c9406d99209a0065a5da4862b86dced18053" }

Payload: {"metadata_type_id":"6","value":"4000"}

Thank you. That’s what I thought it should be. I’ve been having trouble making mine work. I must have a typo or something.

@dbayer Do you also get an error saying document_type_id: a valid integer is required?

Are you referring to the file upload or the metadata update?

I believe you need an existing document to upload the file to. That is probably where the error is coming from.

For uploading the file, I used this URL (where 72 is the document ID)

https://test.mywebsite.com/api/v4/documents/72/files/
Body Parameters
Form-Data
file_new: binary file
action_name: append (in this example, I already had one file in the document)

For the Metadata update I used this URL (where 72 is the Document ID)

https://test.mywebsite.com/api/v4/documents/72/metadata/
Body Parameters
metadata_type_id: meta data type id
value: meta data

Ah I tried the somewhat new /documents/upload endpoint. But maybe this one is currently broken or does not work how I thought it would.

I can’t check right now, but I think that also needs an existing document to point to.

Don’t forget you can view and test the REST API in the Tools Menu in Mayan.