Using the HTTP Action to POST Multipart Form Data in Power Automate & Logic Apps

A common way 3rd party APIs handle file uploads is via an HTTP multi-part form. The API documentation might point to some dry RFC documentation like this

The short of it is you must invoke an HTTP POST request with the body formatted, to use the example in the linked document, like this:

        Content-type: multipart/form-data, boundary=AaB03x

        --AaB03x
        content-disposition: form-data; name="field1"

        Joe Blow
        --AaB03x
        content-disposition: form-data; name="pics"; filename="file1.txt"
        Content-Type: text/plain

         ... contents of file1.txt ...
        --AaB03x--

In the Power Automate & Logic Apps HTTP action you can write this plain text directly into the body, and it works OK as long as the file content is plain text.

I’m using Bamboo HR as an example below. In this screen shot, the upload works fine, if you want to create a file called readme.txt containing “This is a sample text file.”

Capture.PNG

But what about binary content? What if I want to upload an image or PDF?

Your first thought might be put some dynamic content where “This is a sample text file.” is:

Capture

Does it work? Well sort of. The HTTP action succeeds and returns a 201 status.

Capture

However, the file in the destination is corrupt. It’s there, and the file size is sort of right, but the file is corrupt.

If you open both files in Notepad you can see something has happened to to the content. The one on the left is the original file and the right is the one downloaded from the upstream service we just posted the file to:

Capture

If you try and convert to base64 you get similar results – a file, but a corrupt one with the contents markedly different from the original when you open in a text editor.

So how do you solve this? There’s isn’t really a great deal of documentation around to help. The nearest you get is the Logic Apps HTTP connector documentation (while this is the Logic Apps documentation, Power Automate is built on Logic Apps so it applies equally there too).

Although this small piece of documentation is highly valuable in solving the problem, it misses a couple of key requirements: Attributes and the schema of the file content.

In my example above the file has several attributes: a category, which in my case is represented by a numeric string, 18, a file name (this is separate from the actual filename within the header of the file section of the multi-part form) and also a boolean, shared, which is represented by a text string “yes”. Also omitted from the Microsoft material is the schema of the body property of the file. They simply put trigger() in their example as the body, with no explanation as to what that actually is.

So how do you construct the JSON body of the HTTP action to include those attributes?

I’ll save you the hassle of finding out by explaining below, but I will say how I found out:

You can create a new Flow that’s triggered on “when an HTTP request is received”. Change the method to POST and then add a compose action. Put the “Body” dynamic content into the compose and save. Copy the URL for the trigger and go back to your original Flow.

Next, change the URL in the HTTP POST action to the one in your clipboard and remove any authentication parameters, then run it.

Your new flow will trigger and in the compose action you should see the multi-part form data received in the POST request. You can now start playing around with the JSON in the HTTP body until you get something that resembles your API documentation.

So, this is the result of that process:

Capture

It’s actually pretty simple when you look at it. The body object has two attributes; $content-type and $multipart.

The HTTP action interprets the $content-type property and puts the right data into the content-type header of the request. There’s actually no need for you to specify any headers at all in your HTTP action, unless for authentication purposes.

The $multipart attribute is an array, and each object in the array is a separate part of the form, so for every point in the plain text you’d put a form boundary, that’s another object in the array.

Each object in the array has a headers and a body attribute. :

——BambooHR-MultiPart-Mime-Boundary—-
Content-Disposition: form-data; name=”category”

112
——BambooHR-MultiPart-Mime-Boundary—-

vs.

{
“body”: “112“,
“headers”: {
Content-Disposition”: “form-data; name=\”category\”
}
}

In the case of the actual file, there’s a quirk. You would expect, based on the following, that there would be two headers; Content-Disposition and Content-Type:

Capture

But that’s actually not so. The JSON must be constructed like as follows. The content-type is part of the body:

{
“body”: {
“$content”:”iVBORw0KGgoAAAANSUhEUgAA…..”,
“$content-type”: “image/jpeg”
},
“headers”: {
“Content-Disposition”: “form-data; name=\”file\”; filename=\”test.jpg\””
}
}

Note the need to use a backslash to break out of the inverted commas within the Content-Disposition header value.

Depending on where you’re getting your content from, you might already have the whole body object in the right format.

For example the output of SharePoint and OneDrive Get file content, and OneDrive’s Convert File actions contain a body with the $content-type and $content attributes, with the $content in Base64 already:

Capture.PNG

So, if you’re using an action like that earlier in your flow, you could just insert the “File content” dynamic content directly into the body like this:

Capture

Your mileage may vary: If you’re getting the file content from a connector that outputs binary, or indeed base64 but not in the same format as the Get file content actions, then you will need include those properties in your HTTP body and possibly convert the dynamic content into base64 first using the base64() function in the expression builder.

You would also need to get your content type from somewhere too, if the upstream API you’re posting to cares about it.

To finish, I need to give some credit to SamPo on the Microsoft Power Automate forums, who figured all this out long before I did and provided great help in my time of need.

If you feel like this has saved you time and frustrations, feel free to leave a small donation via Paypal:

7 Comments

  1. be awesome if you could post something about PDF files. Seems they need to be in binary format to be readable on the other end yet the JSON of the body doesnt parse if you use base64tobinary on the file content…. any tips!?

    Like

    1. It might be a limitation of your upstream API. I’ve successfully posted base64 encoded PDFs to the APIs I have to deal with using this technique. The HTTP request body is plain text so putting binary content directly in there is never going to work.

      Like

      1. Thanks Wilheim, we did get it working, we got it to work by changing “$content-type”: “multipart/form-data” to “$content-type”: “multipart/mixed”. Then base64 encoded file contents posted fine via the body. Thank you so much for replying, much appreciated.

        Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s