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
397

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

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
0
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 correctly post json as multipart form data with python requests - Stack Overflow
The requests.post function has keyword parameter json which can take a simple Python dict. This is usually my way of posting json... ... Why in the world would you switch to multipart/form-data if you expect to send complex/nested data to it? More on stackoverflow.com
🌐 stackoverflow.com
May 30, 2017
Custom multipart/form-data Content-Type header silently discards file upload
{ "args": {}, "data": "", "files": ... "154247", "Content-Type": "multipart/form-data; boundary=------------------------", "Host": "httpbin.org", "User-Agent": "python-requests/2.18.4" }, "json": null, "url": "https://httpbin.org/post" }... More on github.com
🌐 github.com
5
March 25, 2018
🌐
Bobby Hadz
bobbyhadz.com › blog › send-multipart-form-data-request-with-requests-in-python
Python: Sending multipart/form-data request with requests | bobbyhadz
April 11, 2024 - When the files parameter is set, requests sends a multipart/form-data POST request instead of a application/x-www-form-urlencoded POST request. ... Copied!from pprint import pprint import requests url = 'https://httpbin.org/post' response = ...
🌐
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 › 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   Cratig
🌐
Requests
requests.readthedocs.io › en › latest › user › quickstart
Quickstart — Requests 2.32.5 documentation - Read the Docs
>>> payload_tuples = [('key1', 'value1'), ('key1', 'value2')] >>> r1 = requests.post('https://httpbin.org/post', data=payload_tuples) >>> payload_dict = {'key1': ['value1', 'value2']} >>> r2 = requests.post('https://httpbin.org/post', data=payload_dict) >>> print(r1.text) { ... "form": { "key1": [ "value1", "value2" ] }, ... } >>> # httpbin may embed non-deterministic metadata, >>> # so we only compare our submitted data here. >>> r1.json()['form'] == r2.json()['form'] True
Find elsewhere
🌐
Stack Overflow
stackoverflow.com › questions › 44260807 › how-to-correctly-post-json-as-multipart-form-data-with-python-requests
How to correctly post json as multipart form data with python requests - Stack Overflow
May 30, 2017 - FYI: i only put header with autorization token inside of requests.post. I don't try to put my boundary or Content type because i know that requests will handle it by itself. And when i send next data into it - everything works cool: data = { "session[email]": (None, "[email protected]"), "session[password]": (None, "examplepassword") } ... data = { "session": { "email": "[email protected]", "password": "examplepassword" } } data = {(None, json.dumps(data)) } ... Content-Type: multipart/form-data; boundary=AAAbbbCCCd ' send: b'--AAAbbbCCCd Content-Disposition: form-data; {"session": {"password": "examplepassword", "email": "[email protected]"}} --8a78b52e98d34fecae72d054e577c8ad-- '
🌐
GeeksforGeeks
geeksforgeeks.org › python › how-to-upload-files-using-python-requests-library
How to Upload Files Using Python Requests Library - GeeksforGeeks
July 23, 2025 - import requests url = 'https://httpbin.org/post' files = {'file': open('file.txt', 'rb')} # Specify the file you want to upload response = requests.post(url, files=files) print(response.text) ... { "args": {}, "data": "", "files": { "file": "GeeksforGeeks" }, "form": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Content-Length": "157", "Content-Type": "multipart/form-data; boundary=c4e3e1f85303491ffd523537762f87bc", "Host": "httpbin.org", "User-Agent": "python-requests/2.31.0", "X-Amzn-Trace-Id": "Root=1-65fbfabc-4d943f6b5851070275fbb946" }, "json": null, "origin": "157.34.0.92", "url": "https://httpbin.org/post" }
🌐
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   wlerin
Top answer
1 of 3
28

You are setting the header yourself, including a boundary. Don't do this; requests generates a boundary for you and sets it in the header, but if you already set the header then the resulting payload and the header will not match. Just drop you headers altogether:

def send_request():
    payload = {"param_1": "value_1", "param_2": "value_2"}
    files = {
         'json': (None, json.dumps(payload), 'application/json'),
         'file': (os.path.basename(file), open(file, 'rb'), 'application/octet-stream')
    }

    r = requests.post(url, files=files)
    print(r.content)

Note that I also gave the file part a filename (the base name of the file path`).

For more information on multi-part POST requests, see the advanced section of the documentation.

2 of 3
0

In case if someone searches ready to use method to transform python dicts to multipart-form data structures here is a simple gist example to do such transformation:

{"some": ["balls", "toys"], "field": "value", "nested": {"objects": "here"}}
    ->
{"some[0]": "balls", "some[1]": "toys", "field": "value", "nested[objects]": "here"}

To send some data you may want to use the multipartify method from this gist like this:

import requests  # library for making requests

payload = {
    "person": {"name": "John", "age": "31"},
    "pets": ["Dog", "Parrot"],
    "special_mark": 42,
}  # Example payload

requests.post("https://example.com/", files=multipartify(payload))

To send same data along with any file (as OP wanted) you may simply add it like this:

converted_data = multipartify(payload)
converted_data["attachment[0]"] = ("file.png", b'binary-file', "image/png")

requests.post("https://example.com/", files=converted_data)

Note, that attachment is a name defined by server endpoint and may vary. Also attachment[0] indicates that it is first file in you request - this is also should be defined by API documentation.

🌐
ProxiesAPI
proxiesapi.com › articles › sending-form-data-with-python-requests
Sending Form Data with Python Requests | ProxiesAPI
JSON is a popular format for passing structured data. To send JSON, pass your Python dict to the ... data = { 'name': 'John Doe', 'hobbies': ['hiking', 'diving'] } response = requests.post(url, json=data)
🌐
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...
🌐
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.
🌐
w3resource
w3resource.com › python-exercises › urllib3 › python-urllib3-exercise-19.php
Python File Upload: Simulate POST request with multipart/Form-Data
import urllib3 # Create a PoolManager http = urllib3.PoolManager() # Specify the file and URL file_path = 'test.txt' upload_url = 'https://example.com/upload' # Prepare the multipart/form-data payload fields = { 'field1': 'value1', # Additional form fields if needed 'field2': 'value2', 'file': ('filename.txt', open(file_path, 'rb').read(), 'text/plain') } # Set the Content-Type header to multipart/form-data headers = {'Content-Type': 'multipart/form-data'} # Make the POST request response = http.request('POST', upload_url, fields=fields, headers=headers) # Receive and print the response print(response.data.decode('utf-8'))
🌐
HayaGeek
hayageek.com › home › send post request with python requests
Send POST Request With Python requests
February 7, 2024 - In this example, we pass a dictionary to the json parameter which automatically encodes the data into JSON format. Submitting files such as images requires multipart/form-data, which the requests library also supports.
🌐
YouTube
youtube.com › watch
python requests post file multipart form data - YouTube
Instantly Download or Run the code at https://codegive.com title: uploading files with python requests using multipart form dataintroduction:in this tutoria...
Published   February 23, 2024
🌐
Pythonrequests
pythonrequests.com › python-requests-post-multipart-form-data-json
python requests post multipart/form-data=json
July 12, 2023 - import requests import json # Choose the file you want to upload file = {'file': open('myfile.csv', 'rb')} # Set content type header to multipart/form-data headers = {'Content-Type': 'multipart/form-data'} # Create dictionary of form data and JSON data data = {'name': 'John Doe', 'age': '30'} json_data = json.dumps(data) # Send request to server using post() method of requests library response = requests.post(url, headers=headers, files=file, data=json_data) print(response.status_code)
🌐
TutorialsPoint
tutorialspoint.com › requests › requests_file_upload.htm
File Upload with Requests in Python
import requests myurl = 'https://httpbin.org/post' files = {'file': ('test1.txt', 'Welcome to TutorialsPoint')} getdata = requests.post(myurl, files=files) print(getdata.text) E:\prequests>python makeRequest.py { "args": {}, "data": "", "files": { "file": "Welcome to TutorialsPoint" }, "form": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Content-Length": "170", "Content-Type": "multipart/form-data; boundary=f2837238286fe40e32080aa7e172be4f", "Host": "httpbin.org", "User-Agent": "python-requests/2.22.0" }, "json": null, "origin": "117.223.63.135, 117.223.63.135", "url": "https://httpbin.org/post" } Print Page ·