Basically, if you specify a files parameter (a dictionary), then requests will send a multipart/form-data POST instead of a application/x-www-form-urlencoded POST. You are not limited to using actual files in that dictionary, however:

>>> import requests
>>> response = requests.post('http://httpbin.org/post', files=dict(foo='bar'))
>>> response.status_code
200

and httpbin.org lets you know what headers you posted with; in response.json() we have:

>>> from pprint import pprint
>>> pprint(response.json()['headers'])
{'Accept': '*/*',
 'Accept-Encoding': 'gzip, deflate',
 'Connection': 'close',
 'Content-Length': '141',
 'Content-Type': 'multipart/form-data; '
                 'boundary=c7cbfdd911b4e720f1dd8f479c50bc7f',
 'Host': 'httpbin.org',
 'User-Agent': 'python-requests/2.21.0'}

And just to be explicit: you should not set the Content-Type header when you use the files parameter, leave this to requests because it needs to specify a (unique) boundary value in the header that matches the value used in the request body.

Better still, you can further control the filename, content type and additional headers for each part by using a tuple instead of a single string or bytes object. The tuple is expected to contain between 2 and 4 elements; the filename, the content, optionally a content type, and an optional dictionary of further headers.

I'd use the tuple form with None as the filename, so that the filename="..." parameter is dropped from the request for those parts:

>>> files = {'foo': 'bar'}
>>> print(requests.Request('POST', 'http://httpbin.org/post', files=files).prepare().body.decode('utf8'))
--bb3f05a247b43eede27a124ef8b968c5
Content-Disposition: form-data; name="foo"; filename="foo"

bar
--bb3f05a247b43eede27a124ef8b968c5--
>>> files = {'foo': (None, 'bar')}
>>> print(requests.Request('POST', 'http://httpbin.org/post', files=files).prepare().body.decode('utf8'))
--d5ca8c90a869c5ae31f70fa3ddb23c76
Content-Disposition: form-data; name="foo"

bar
--d5ca8c90a869c5ae31f70fa3ddb23c76--

files can also be a list of two-value tuples, if you need ordering and/or multiple fields with the same name:

requests.post(
    'http://requestb.in/xucj9exu',
    files=(
        ('foo', (None, 'bar')),
        ('foo', (None, 'baz')),
        ('spam', (None, 'eggs')),
    )
)

If you specify both files and data, then it depends on the value of data what will be used to create the POST body. If data is a string, only it willl be used; otherwise both data and files are used, with the elements in data listed first.

There is also the excellent requests-toolbelt project, which includes advanced Multipart support. It takes field definitions in the same format as the files parameter, but unlike requests, it defaults to not setting a filename parameter. In addition, it can stream the request from open file objects, where requests will first construct the request body in memory:

from requests_toolbelt.multipart.encoder import MultipartEncoder

mp_encoder = MultipartEncoder(
    fields={
        'foo': 'bar',
        # plain file object, no filename or mime type produces a
        # Content-Disposition header with just the part name
        'spam': ('spam.txt', open('spam.txt', 'rb'), 'text/plain'),
    }
)
r = requests.post(
    'http://httpbin.org/post',
    data=mp_encoder,  # The MultipartEncoder is posted as data, don't use files=...!
    # The MultipartEncoder provides the content-type header with the boundary:
    headers={'Content-Type': mp_encoder.content_type}
)

Fields follow the same conventions; use a tuple with between 2 and 4 elements to add a filename, part mime-type or extra headers. Unlike the files parameter, no attempt is made to find a default filename value if you don't use a tuple.

Answer from Martijn Pieters on Stack Overflow
Top answer
1 of 16
398

Basically, if you specify a files parameter (a dictionary), then requests will send a multipart/form-data POST instead of a application/x-www-form-urlencoded POST. You are not limited to using actual files in that dictionary, however:

>>> import requests
>>> response = requests.post('http://httpbin.org/post', files=dict(foo='bar'))
>>> response.status_code
200

and httpbin.org lets you know what headers you posted with; in response.json() we have:

>>> from pprint import pprint
>>> pprint(response.json()['headers'])
{'Accept': '*/*',
 'Accept-Encoding': 'gzip, deflate',
 'Connection': 'close',
 'Content-Length': '141',
 'Content-Type': 'multipart/form-data; '
                 'boundary=c7cbfdd911b4e720f1dd8f479c50bc7f',
 'Host': 'httpbin.org',
 'User-Agent': 'python-requests/2.21.0'}

And just to be explicit: you should not set the Content-Type header when you use the files parameter, leave this to requests because it needs to specify a (unique) boundary value in the header that matches the value used in the request body.

Better still, you can further control the filename, content type and additional headers for each part by using a tuple instead of a single string or bytes object. The tuple is expected to contain between 2 and 4 elements; the filename, the content, optionally a content type, and an optional dictionary of further headers.

I'd use the tuple form with None as the filename, so that the filename="..." parameter is dropped from the request for those parts:

>>> files = {'foo': 'bar'}
>>> print(requests.Request('POST', 'http://httpbin.org/post', files=files).prepare().body.decode('utf8'))
--bb3f05a247b43eede27a124ef8b968c5
Content-Disposition: form-data; name="foo"; filename="foo"

bar
--bb3f05a247b43eede27a124ef8b968c5--
>>> files = {'foo': (None, 'bar')}
>>> print(requests.Request('POST', 'http://httpbin.org/post', files=files).prepare().body.decode('utf8'))
--d5ca8c90a869c5ae31f70fa3ddb23c76
Content-Disposition: form-data; name="foo"

bar
--d5ca8c90a869c5ae31f70fa3ddb23c76--

files can also be a list of two-value tuples, if you need ordering and/or multiple fields with the same name:

requests.post(
    'http://requestb.in/xucj9exu',
    files=(
        ('foo', (None, 'bar')),
        ('foo', (None, 'baz')),
        ('spam', (None, 'eggs')),
    )
)

If you specify both files and data, then it depends on the value of data what will be used to create the POST body. If data is a string, only it willl be used; otherwise both data and files are used, with the elements in data listed first.

There is also the excellent requests-toolbelt project, which includes advanced Multipart support. It takes field definitions in the same format as the files parameter, but unlike requests, it defaults to not setting a filename parameter. In addition, it can stream the request from open file objects, where requests will first construct the request body in memory:

from requests_toolbelt.multipart.encoder import MultipartEncoder

mp_encoder = MultipartEncoder(
    fields={
        'foo': 'bar',
        # plain file object, no filename or mime type produces a
        # Content-Disposition header with just the part name
        'spam': ('spam.txt', open('spam.txt', 'rb'), 'text/plain'),
    }
)
r = requests.post(
    'http://httpbin.org/post',
    data=mp_encoder,  # The MultipartEncoder is posted as data, don't use files=...!
    # The MultipartEncoder provides the content-type header with the boundary:
    headers={'Content-Type': mp_encoder.content_type}
)

Fields follow the same conventions; use a tuple with between 2 and 4 elements to add a filename, part mime-type or extra headers. Unlike the files parameter, no attempt is made to find a default filename value if you don't use a tuple.

2 of 16
147

Requests has changed since some of the previous answers were written. Have a look at this Issue on Github for more details and this comment for an example.

In short, the files parameter takes a dictionary with the key being the name of the form field and the value being either a string or a 2, 3 or 4-length tuple, as described in the section POST a Multipart-Encoded File in the Requests quickstart:

>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}

In the above, the tuple is composed as follows:

(filename, data, content_type, headers)

If the value is just a string, the filename will be the same as the key, as in the following:

>>> files = {'obvius_session_id': '72c2b6f406cdabd578c5fd7598557c52'}

Content-Disposition: form-data; name="obvius_session_id"; filename="obvius_session_id"
Content-Type: application/octet-stream

72c2b6f406cdabd578c5fd7598557c52

If the value is a tuple and the first entry is None the filename property will not be included:

>>> files = {'obvius_session_id': (None, '72c2b6f406cdabd578c5fd7598557c52')}

Content-Disposition: form-data; name="obvius_session_id"
Content-Type: application/octet-stream

72c2b6f406cdabd578c5fd7598557c52
🌐
Reddit
reddit.com › r/learnpython › python requests - multipart form data
r/learnpython on Reddit: Python Requests - multipart form data
February 14, 2022 -

I am new to python requests and python in general - I have a somewhat intermediate java background.

I am trying to figure out why this post request throws a 400 error when trying to upload a file.

Any hints?

def upload_a_document(self, library_id, folder_id, filepath):
        url = "https://xxx" \
              "/{library_id}!{folder_id}/documents".format(
            server=self.server,
            customer_id=self.customer_id,
            library_id=library_id,
            folder_id=folder_id)
        params = \
            {
                "profile":
                    {
                        "doc_profile": {
                            "access": "full_access",
                            "comment": f"{filepath}",
                            "database": f"{library_id}",
                            "default_security": "public",
                            "name": f"{os.path.basename(filepath)}",
                            "size": os.path.getsize(filepath),
                            "type": "WORDX",
                            "type_description": "WORD 2010",
                            "wstype": "document"
                        },
                        "warnings_for_required_and_disabled_fields": True
                    },
                "file": {open(f"{filepath}", "rb").read()}
            }
        payload = ""

        # encoded formdata for multipart
        encoded_formdata = encode_multipart_formdata(params)

        headers = {'X-Auth-Token': self.x_auth_token, 'Content-Type': encoded_formdata[1]}
        response = requests.request("POST", url, data=payload, headers=headers, params=params)
        myJSON = response.json()
        print(json.dumps(myJSON, indent=4))

Getting this error -

requests.exceptions.JSONDecodeError: [Errno Expecting value] <html><body><h1>400 Bad request</h1>

Your browser sent an invalid request.

Discussions

Python Requests: Post JSON and file in single request - Stack Overflow
I need to do a API call to upload a file along with a JSON string with details about the file. I am trying to use the python requests lib to do this: import requests info = { 'var1' : 'this'... More on stackoverflow.com
🌐 stackoverflow.com
Help with multipart-formdata - Python Requests code
Hi, When I create a form-data POST request with two images, the Postman Request works. However when I select the code for Python - Requests the generated code does not work at all. It’s looking for the boundary markers… Is there a way to see the full code that Postman generates that succeeds ? More on community.postman.com
🌐 community.postman.com
1
0
September 24, 2020
requests with multiparts using data and files doesn't have content-type in data part
Trying to debug why a Postman request was working but Python wasn't, I captured the request data (using requestcatcher.com) and below are the two parts of the requests that should be the same: Postman: ----------------------------166390214727573301705303 Content-Disposition: form-data; name="documents_input" Content-Type: application/json... More on github.com
🌐 github.com
4
July 19, 2023
How to add JSON and file in multipart/form-data body to a POST API request?
Basically this works in Postman but can’t make it work in UiPath: Similar to this: how-to-send-a-body-type-form-data-using-post-in-a-http-request. However, answer was for a file and string, not for JSON. I keep on ge… More on forum.uipath.com
🌐 forum.uipath.com
4
0
February 24, 2022
🌐
Requests
requests.readthedocs.io › en › latest › user › quickstart
Quickstart — Requests 2.34.2 documentation
1 month ago - Note, the json parameter is ignored if either data or files is passed. Requests makes it simple to upload Multipart-encoded files: >>> url = 'https://httpbin.org/post' >>> files = {'file': open('report.xls', 'rb')} >>> r = requests.post(url, files=files) >>> r.text { ...
🌐
Postman
community.postman.com › help hub
Help with multipart-formdata - Python Requests code - Help Hub - Postman Community
September 24, 2020 - Hi, When I create a form-data POST request with two images, the Postman Request works. However when I select the code for Python - Requests the generated code does not work at all. It’s looking for the boundary marker…
🌐
GitHub
github.com › psf › requests › issues › 1081
Generate multipart posts without a file · Issue #1081 · psf/requests
Currently, the only way to have a multipart form request is r = requests.post(url, data=payload, files=files) which may have a component --3eeaadbfda0441b8be821bbed2962e4d Content-Disposition: form-data; name="file"; filename="filename.t...
Author   psf
Find elsewhere
🌐
Stack Abuse
stackabuse.com › bytes › how-to-send-multipart-form-data-with-requests-in-python
How to Send "multipart/form-data" with Requests in Python
January 29, 2025 - This code creates an HTTPS connection to "example.com", sends a POST request with our file as multipart/form-data, and then prints the response from the server. In this Byte, you've seen how to send multipart/form-data with the Python requests library, handle potential errors, and also looked at some common errors and their solutions.
🌐
GitHub
github.com › psf › requests › issues › 6484
requests with multiparts using data and files doesn't have content-type in data part · Issue #6484 · psf/requests
July 19, 2023 - --285101b8d40c60d675e8f54e937ceb3e Content-Disposition: form-data; name="files"; filename="removed.pdf" Content-Type: application/pdf · As you can see, the content type displays correctly in Postman but via Python requests it does on the PDF but not the data=jsonDate · The, hacky, work-a-round for this was to shove the json data alongside the files= component: multipart_data = { "documents_input": (None, documentInputPayload, "application/json"), "files": files["files"] }
Author   psf
🌐
Franklingu
franklingu.github.io › programming › 2017 › 10 › 30 › post-multipart-form-data-using-requests
Post multipart form data using Python requests | Junchao's blog
October 30, 2017 - In [1]: data_req = Request('POST', 'https://franklingu.github.io/', data={'name ...: ': 'normal'}).prepare() In [2]: print(data_req.body) name=normal In [3]: normal_multipart_req = Request('POST', 'https://franklingu.github.io/', ...: files={'name': open('test.txt', 'r'), 'name2': 'content'}).pre ...: pare() In [4]: print(normal_multipart_req.body.decode('utf-8')) --cdf39af4e1bf449384b62fef701eda7b Content-Disposition: form-data; name="name"; filename="name" test --cdf39af4e1bf449384b62fef701eda7b Content-Disposition: form-data; name="name2"; filename="name2" content --cdf39af4e1bf449384b62fef
🌐
UiPath Community
forum.uipath.com › help › activities
How to add JSON and file in multipart/form-data body to a POST API request? - Activities - UiPath Community Forum
Basically this works in Postman but can’t make it work in UiPath: Similar to this: how-to-send-a-body-type-form-data-using-post-in-a-http-request. However, answer was for a file and string, not for JSON. I keep on getting this error in the response: { "error": { "code": "NRC_INVALID_PARAM", "code_message": "Invalid parameter for request", "details": "No JSON object could be decoded", "message": "Invalid parameter for request" } }
Published   February 24, 2022
🌐
Quora
quora.com › How-do-you-pass-form-data-in-Python-requests
How to pass form data in Python requests - Quora
Answer: It depends whether the form expects data in a POST or a GET request - most likely a POST. the way you format the data is different between the two. So you need to know: 1. What are all the field names on the form, including any hidden fields that you need to include - (beware some of t...
🌐
HappyFox
support.happyfox.com › kb › article › 1027-sending-request-for-ticket-creation-using-multipart-form-data-in-python
Sending Request for Ticket creation using MultiPart Form data in Python - HappyFox Support
July 31, 2020 - To get the <BASIC auth> from your ... ticket creation: https://<account_name/instance_name>.happyfox.com/api/1.1/json/tickets/ Method: POST content-type: multipart/form-data...
🌐
Apify
blog.apify.com › python-post-request
How to post JSON data with Python Requests
December 7, 2025 - The content type is automatically set to a specific type, and the data is sent as form data. Look for the keyword "form" in the output for confirmation. If you’ve ever needed to send files to a server using a POST request, you’ve probably come across multipart/form-data.
🌐
GitHub
github.com › psf › requests › issues › 4554
Custom multipart/form-data Content-Type header silently discards file upload · Issue #4554 · psf/requests
March 25, 2018 - { "args": {}, "data": "", "files": { "logo": "data:image/jpeg;base64,<base64 image data>" }, "form": {}, "headers": { "Accept": "application/json", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Content-Length": "154247", "Content-Type": "multipart/form-data; boundary=------------------------<some boundary>", "Host": "httpbin.org", "User-Agent": "python-requests/2.18.4" }, "json": null, "url": "https://httpbin.org/post" } { "args": {}, "data": "", "files": {}, "form": {}, "headers": { "Accept": "application/json", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Content
Author   psf
🌐
Swagger
swagger.io › docs › specification › v3_0 › describing-request-body › multipart-requests
Multipart Requests | Swagger Docs
November 12, 2018 - You typically use these requests ... with a JSON object). In OpenAPI 3, you describe a multipart request in the following way: ... You start with the requestBody/content keyword. Then, you specify the media type of request data. File uploads typically use the _multipart/form-data_ media ...
🌐
ProxiesAPI
proxiesapi.com › articles › setting-the-content-type-header-for-python-requests
Setting the Content-Type Header for Python Requests | ProxiesAPI
import requests import json data = {'key1': 'value1', 'key2': 'value2'} headers = {'Content-Type': 'application/json'} response = requests.post(url, headers=headers, data=json.dumps(data)) The key points here: Import the · json module to serialize the Python dict to JSON · Set headers to include · 'Content-Type': 'application/json' Use · json.dumps() on the data to convert to JSON string · When uploading files, you can use multipart form data encoding and should set the content type accordingly: Copy ·
🌐
Google Developer forums
googlecloudcommunity.com › google cloud › apigee
How to transform json payload in multipart/form-data request - Apigee - Google Developer forums
March 11, 2021 - Hello Currently i’m trying to implement a proxy where i receive multipart/form-data request with two form-datas 1\ a JSON payload 2\ a binary payload (file) My objective is to leave the binary payload untouched and f…
🌐
Anvil
anvil.works › anvil q&a
How to fetch the image from a multpart/form-data post request? - Anvil Q&A - Anvil Community Forum
February 22, 2023 - Hi. I have a running API that receives an image posted from JavaScript component. I would like to store the image in anvil and return a link to the stored image. The data is posted as multipart/form-data and I can only control the receiving end. When storing the body as is it looks like this ...
🌐
JetBridge
blog.jetbridge.com › home › multipart-encoded and python requests
Multipart-Encoded and Python Requests - JetBridge Software
September 28, 2021 - &gt;&gt;&gt; headers = {'x-auth-api-key': &lt;SOME_TOKEN&gt;, 'Content-type': 'multipart/form-data'} &gt;&gt;&gt; url = 'https://httpbin.org/post' &gt;&gt;&gt; files = {'file': open('report.xls', 'rb')} &gt;&gt;&gt; r = requests.post(url, ...
🌐
Latenode
community.latenode.com › other questions › zapier
How to send multipart form data with file upload using Python in Zapier automation - Zapier - Latenode Official Community
August 3, 2025 - I need help with sending file uploads through Python code in Zapier. I’m trying to automate file submissions to an external API but running into issues. What works: I can successfully send files using Postman with this Python code: import requests api_endpoint = "https://api.example.com/upload/files" params = {"access_token": "sample_token_12345", "filename": "document.pdf"} form_data = "------CustomBoundary123\r\nContent-Disposition: form-data; name=\"upload\"; filename=\"sample.png\"\r\nC...