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
🌐
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'))
Discussions

Python Requests - multipart form data
Looks like the API expects a request structured like this. More on reddit.com
🌐 r/learnpython
6
1
February 14, 2022
POST with multiple files with the form-data sends only 1 file.
POSTing multiple files with a form-data. Using this doc: https://requests.readthedocs.io/en/latest/user/advanced/#post-multiple-multipart-encoded-files However only 1 file is received by the endpoint. But if you change the files array (d... More on github.com
🌐 github.com
4
February 24, 2024
Handling POST data with API Gateway + Lambda?
I recommend working through guid to get a better idea how you would go about implementing what you want: https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started.html More on reddit.com
🌐 r/aws
3
5
July 11, 2015
How to encode multipart/form-data to POST files with urllib2
MultipartPostHandler is another option, adds a handler for urllib2 openers. import urllib2, MultipartPostHandler opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler) response = opener.open("http://url.to.post.to/upload/", {"file": open("filename.ext").read()) You might need to build the request with the content-type multipart/form-data. More on reddit.com
🌐 r/Python
1
4
September 22, 2013
🌐
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 - Let's see how we can use it to send multipart/form-data. Before we start, make sure you have the Requests library installed. If not, you can add it to your Python environment using pip: ... Let's say we need to upload a profile picture to a server. Here's a simple script that does that: import requests url = "http://example.com/upload" file_path = "/path/to/your/file.jpg" with open(file_path, "rb") as file: files = {'file': file} response = requests.post(url, files=files) print(response.status_code)
🌐
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.

🌐
GitHub
gist.github.com › vladwa › ba0ba632c0dcda33354c2d5e833672aa
Python snippet to invoke HTTP Post request with file attached(multipart/form-data) in the body of the request. This function returns Response object. · GitHub
Python snippet to invoke HTTP Post request with file attached(multipart/form-data) in the body of the request. This function returns Response object. - PostFileAttachment.py
🌐
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
🌐
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 ·
Find elsewhere
🌐
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 - Copied!import requests url = 'https://httpbin.org/post' files = {'file': open('data.json', 'rb')} response = requests.post(url, files=files, timeout=30) print(response.text) print(response.status_code) The example assumes that you have a data.json file in the same directory as your Python script.
🌐
ProxiesAPI
proxiesapi.com › articles › sending-multipart-form-data-with-python-requests
Sending Multipart Form Data with Python Requests | ProxiesAPI
However, you may run into issues properly formatting the request in the Python · requests library. Here are some troubleshooting tips for sending multipart form data with Requests. To send a multipart form request with Requests, you need to create a ... import requests url = 'https://api.example.com/upload' data = { 'description': 'My File', 'extra': 'Some extra data' } files = { 'file_upload': open('report.pdf', 'rb') } r = requests.post(url, data=data, files=files)
🌐
GeeksforGeeks
geeksforgeeks.org › python › how-to-upload-files-using-python-requests-library
How to Upload Files Using Python Requests Library - GeeksforGeeks
July 23, 2025 - In this example, below Python code sends a POST request to "https://httpbin.org/post", including both form data ('key': 'value') and a file ('file.txt') uploaded via the `requests` library. It then prints the response received from the server. Python3 · import requests url = 'https://httpbin.org/post' data = {'key': 'value'} # Additional data if required with open('file.txt', 'rb') as file: response = requests.post(url, data=data, files={'file': file}) print(response.text) Output ·
🌐
JetBridge
blog.jetbridge.com › home › multipart-encoded and python requests
Multipart-Encoded and Python Requests - JetBridge Software
September 28, 2021 - The recommended header for multipart-encoded files/images is multipart/form-data and requests already set it for us automatically, using the parameter “files”. Here’s an example taken from requests documentation: &gt;&gt;&gt; url = ...
🌐
10xdev
10xdev.blog › python-requests-upload-file-post-multipart-form-data
Post Multipart Form Data in Python with Requests: Flask File Upload Example |
Next, create a templates folder and add an index.html file with the following code: <!DOCTYPE html> <html> <head> <title>Upload New File</title> </head> <body> <h1>Upload Files</h1> <form action="handle_form" method="post" enctype="multipart/form-data"> <input type="file" name="file"> <input type="submit" value="Upload"> </form> </body> </html>
🌐
GitHub
github.com › psf › requests › issues › 6648
POST with multiple files with the form-data sends only 1 file. · Issue #6648 · psf/requests
February 24, 2024 - import requests url = "https://endpoint.com/api/upload" payload = {} files=[ ('images',('image01.png',open('H:/image01.png','rb'),'image/png')), ('images',('image02.png',open('H:/image02.png','rb'),'image/png')) ] headers = {} response = requests.post(url, files=files) print(response.text)
Author   geekbeard
🌐
GitHub
gist.github.com › srafay › 19e0a13fe7e402f0a79715b1ed3f6560
Python Request for form-data · GitHub
To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters ... Hey! Great script! Would you know how to send the datepicker in the form data to check Electricity Balance in this webpage: https://www.centrodeinformacao.ren.pt/PT/InformacaoExploracao/Paginas/EstatisticaDiaria.aspx · The value is sent in here but I dont know how to send it in a request.post:
🌐
ProxiesAPI
proxiesapi.com › articles › sending-form-data-with-python-requests
Sending Form Data with Python Requests | ProxiesAPI
files = { 'file1': ('report.pdf', open('report.pdf','rb'), 'application/pdf'), 'field': (None, 'value') } The Requests module handles all the complexity of multipart encoding behind the scenes. 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)
🌐
YouTube
youtube.com › watch
python requests post form data file - YouTube
Instantly Download or Run the code at https://codegive.com title: uploading files with python requests: a step-by-step tutorialintroduction:when working wit...
Published   February 23, 2024
🌐
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 create tickets in your HappyFox account via API using multipart/form data, please follow the below sample code written in python. ... company_name = <add_your_company_name/Instance_name> url = "https://" + company_name + ".happyfox.com/api/1.1/json/tickets/" payload = ... { 'Authorization': '<BASIC auth>', 'Content-Type': 'multipart/form-data; boundary=--------------------------531818130631649698349478' } response = requests.request("POST", url, headers=headers, data = payload, files = files) print(response.text.encode('utf8'))
🌐
Newtum
apitest.newtum.com › examples › python-requests-with-multipart-form-data
How to Send Multipart/Form-Data with Python Requests | API Navigator
files = {'report': ('report.csv', 'col1,col2\nval1,val2\n', 'text/csv')} response = requests.post(url, data=form_data, files=files) print("Status Code:", response.status_code) # The response from httpbin.org will show the parsed form and files print("Response JSON:") print(json.dumps(response.json(), indent=2)) CopyRun
🌐
Medium
techkamar.medium.com › sending-form-data-post-using-python-requests-library-55850c7a93d5
Sending FORM data POST using Python Requests library - tech kamar - Medium
June 26, 2024 - Sending FORM data POST using Python Requests library I regularly use python requests library at work. Most times, I used JSON as medium for communication between client and server. Recently, I had a …
🌐
Semicolonworld
semicolonworld.com › question › 43327 › how-to-send-a-ldquo-multipart-form-data-rdquo-with-requests-in-python
Redirecting...
Learn Web Development,PHP,MySQL,javaScript,jQuery,Ajax,Wordpress,Drupal,Codelgniter,CakePHP With SemicolonWorld Tutorial. View Live Demo And Download Source Code.