Developer Guides

Python Requests POST: JSON, Forms and File Uploads

Send Python Requests POST bodies as JSON, forms or multipart files. Inspect local echo results, avoid encoding mistakes and handle uncertain task submissions.

Download example files
Python Requests json, data and files arguments mapped to JSON, form and multipart receiver formats.

For a Python Requests POST, choose the request-body format the destination expects. Use json= for a Python object that should become JSON, data= for form fields or an explicit body, and files= for a multipart upload. Those choices affect the bytes and Content-Type sent to the server.

This guide uses a local echo server so you can inspect the submitted content without sending example data to an outside service. Start the supplied demo_server.py with python3 demo_server.py, and install Requests with python -m pip install requests.

Workflow at a glance

  1. 1. Send a JSON object

    json= serializes the object and supplies a JSON Content-Type unless you override it. Pass a Python dictionary or list, not a string you have already serialized, unless the API specifically expects a JSON string value.

  2. 2. Send form fields

    The dictionary is form-encoded. The echo fixture represents values as lists to preserve repeated keys. The ampersand in desk & lamp remains part of the value rather than becoming a field separator.

  3. 3. Upload a file as multipart data

    Create a small sample.csv containing fictional data, then use:

  4. 4. Use the body format the endpoint documents

    Do not combine json= with a separate data= or files= payload and assume all of them become one JSON request. Choose one body format, except for the documented multipart combination of form fields and files.

  5. 5. Inspect the request and response separately

    The request's Content-Type describes what you sent. The response's Content-Type describes what came back. Inspect actual bodies only when they do not contain private data; avoid logging credentials or personal records while debugging.

  6. 6. Handle errors without duplicating an operation

    A timeout does not establish that a POST failed before reaching the server. Before retrying a task creation, payment or other operation with side effects, follow the service's idempotency or lookup mechanism.

1. Send a JSON object

import requests

url = "http://127.0.0.1:8766/echo"
payload = {"category": "office", "limit": 3}
response = requests.post(url, json=payload, timeout=(3, 15))
response.raise_for_status()
print(response.json()["json"])
# {'category': 'office', 'limit': 3}

json= serializes the object and supplies a JSON Content-Type unless you override it. Pass a Python dictionary or list, not a string you have already serialized, unless the API specifically expects a JSON string value.

Setting Content-Type: application/json by itself does not convert a Python dictionary into a JSON body. The encoding choice still matters. Requests documents its JSON and form handling in the quickstart.

2. Send form fields

response = requests.post(
    "http://127.0.0.1:8766/echo",
    data={"category": "office", "query": "desk & lamp"},
    timeout=(3, 15),
)
response.raise_for_status()
print(response.json()["form"])
# {'category': ['office'], 'query': ['desk & lamp']}

The dictionary is form-encoded. The echo fixture represents values as lists to preserve repeated keys. The ampersand in desk & lamp remains part of the value rather than becoming a field separator.

For repeated field names, use a list of tuples such as [('tag', 'office'), ('tag', 'lighting')]. A normal dictionary cannot express two independent entries with the same key.

3. Upload a file as multipart data

Create a small sample.csv containing fictional data, then use:

from pathlib import Path
import requests

Path("sample.csv").write_text("id,name\np1,Travel Mug\n", encoding="utf-8")
with open("sample.csv", "rb") as handle:
    response = requests.post(
        "http://127.0.0.1:8766/echo",
        data={"source": "tutorial"},
        files={"file": ("sample.csv", handle, "text/csv")},
        timeout=(3, 15),
    )
response.raise_for_status()
print(response.json()["parts"])

Let Requests construct the multipart Content-Type and boundary. Manually setting only multipart/form-data can omit the boundary the server needs to parse the body. Open files in binary mode and keep the handle open until the request finishes.

The field name file is an example. Match the destination API's expected field name, filename constraints and upload limit.

4. Use the body format the endpoint documents

Destination expects

Requests argument

Common mistake

JSON object or array

json=payload

Passing an already encoded JSON string as if it were an object

URL-encoded form

data=fields

Adding a JSON Content-Type to a form body

Exact text or binary body

data=text_or_bytes

Assuming Requests will infer the required content type

Multipart fields and files

data=fields, files=files

Supplying an incomplete multipart boundary header

Do not combine json= with a separate data= or files= payload and assume all of them become one JSON request. Choose one body format, except for the documented multipart combination of form fields and files.

Python Requests json, data and files arguments mapped to JSON, form and multipart receiver formats.
The local receiver distinguishes JSON, URL-encoded form fields and multipart file parts.

5. Inspect the request and response separately

print(response.request.method)
print(response.request.headers.get("Content-Type"))
print(response.status_code)
print(response.headers.get("Content-Type"))

The request's Content-Type describes what you sent. The response's Content-Type describes what came back. Inspect actual bodies only when they do not contain private data; avoid logging credentials or personal records while debugging.

Call raise_for_status() for HTTP failures, then validate the API's own success fields. A valid JSON response can describe an application error. A successful HTTP request can also return no body; a 204 response should not be passed unconditionally to response.json().

6. Handle errors without duplicating an operation

Problem

Check first

400 Bad Request

Required fields, types, body syntax and the service's error message

401 or 403

Authentication and permission for this operation

415 Unsupported Media Type

Body encoding and Content-Type

JSON decode failure

Response status, response type and whether a body exists

Timeout after submission

Whether the server already accepted or completed the operation

A timeout does not establish that a POST failed before reaching the server. Before retrying a task creation, payment or other operation with side effects, follow the service's idempotency or lookup mechanism.

For MIYAIP Crawler API, a POST creates an asynchronous task. Keep the returned task ID and query it rather than resubmitting while it is running. Its task guide defines the real request envelope and authentication.

Frequently asked questions

Should I use data=json.dumps(payload)?

That sends an explicit body but does not automatically add the JSON Content-Type. Use json=payload for the common object-to-JSON case, or deliberately set both body bytes and headers when exact encoding matters.

Does timeout=(3, 15) limit the entire task to 18 seconds?

No. It configures connection and read waiting, not a total wall-clock deadline. Redirects, retries and ongoing transfer behavior can extend elapsed time.

Can a Session help with several POST requests?

A Session can reuse connections and persist selected state such as cookies. It does not make a non-idempotent POST safe to repeat or validate the response's business meaning.

Once a local echo confirms your body format, use the destination's documented parameters. For MIYAIP, follow the Crawler API interface definition and retrieve the resulting task by ID.

Apply the request format to a real collection task