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
🌐
Requests
requests.readthedocs.io › en › latest › user › quickstart
Quickstart — Requests 2.34.2 documentation
1 month ago - >>> url = 'https://httpbin.org/post' >>> files = {'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')} >>> r = requests.post(url, files=files) >>> r.text { ... "files": { "file": "some,data,to,send\\nanother,row,to,send\\n" }, ... } In the event you are posting a very large file as a multipart/form-data request, you may want to stream the request.
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
Discussions

How to send multipart form data with file upload using Python in Zapier automation
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/... More on community.latenode.com
🌐 community.latenode.com
0
0
August 3, 2025
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
Python requests.post multipart/form-data - Stack Overflow
I have to use a REST API to upload files and information to a server. That API uses a multipart-form, but I cannot seem to be able to use it correctly. Here is the information I use according to t... More on stackoverflow.com
🌐 stackoverflow.com
curl - How to pass multipart/form-data into Python POST Request - Stack Overflow
I am trying to translate this curl command into a python POST request. I am new to calling api endpoints and this is my first encounter of -F form data. The curl request I am trying to replicate is... More on stackoverflow.com
🌐 stackoverflow.com
🌐
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.

🌐
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
🌐
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
🌐
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, ...
Find elsewhere
🌐
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...
🌐
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.
🌐
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…
🌐
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'))
🌐
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...
🌐
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
🌐
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)
🌐
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'))
🌐
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 uses the requests library to perform a POST request to https://httpbin.org/post, uploading the file example.txt with the specified content type of multipart/form-data.
🌐
FastAPI
fastapi.tiangolo.com › tutorial › request-forms-and-files
Request Forms and Files - FastAPI
To receive uploaded files and/or form data, first install python-multipart. Make sure you create a virtual environment, activate it, and then install it, for example: ... from typing import Annotated from fastapi import FastAPI, File, Form, UploadFile app = FastAPI() @app.post("/files/") async ...
🌐
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 - import requests url = "http://localhost:8080/login" form_data = {"username":"johndoe", "password": "user@1234"} resp = requests.post(url, data=form_data)