Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use Requests’ json= parameter: pass a Python dictionary, list, or other JSON-serializable object directly to requests.post(). Requests serializes it and applies the JSON request workflow for you.

import requests

url = "https://api.example.com/items"
payload = {"name": "Alice", "active": True}

response = requests.post(url, json=payload, timeout=10)
response.raise_for_status()
result = response.json()
print(result)

This pattern avoids the most common mistakes: manually encoding JSON, sending the wrong Content-Type, waiting forever for a server, or parsing an error response as if it were success.

The recommended way to post JSON

For a JSON API, put your Python object in json=payload and provide a finite timeout. Requests handles serialization and the JSON content workflow.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import requests

url = "https://api.example.com/items"
payload = {
    "name": "Alice",
    "active": True,
    "tags": ["new", "priority"]
}

response = requests.post(
    url,
    json=payload,
    timeout=10
)

response.raise_for_status()
result = response.json()
print(result)

The payload can contain nested dictionaries, lists, strings, numbers, booleans, and None values that are valid JSON equivalents. The Requests API describes json as a JSON-serializable Python object sent in the request body.

Why json= is preferable

  • You pass Python data rather than maintaining a separate JSON string.
  • Requests performs the serialization step.
  • The request uses the JSON content workflow instead of dictionary form encoding.
  • Your code remains easy to read and change as the payload grows.

Requests documentation currently identifies release 2.34.2 and officially supports Python 3.10 and newer. Check the installed version in the same environment where your script runs if behavior differs from your deployment.

json= versus data= and files=

These parameters represent different body formats. Choose one deliberately.

Goal Requests call What is sent
JSON API body requests.post(url, json=payload) Requests serializes the object using its JSON workflow.
Form submission requests.post(url, data=form_data) A dictionary is form-encoded.
Multipart upload requests.post(url, files=files) Files are encoded as multipart data.
Already serialized body requests.post(url, data=json_text) You control the serialized text and headers.

The parameter interaction that causes silent mistakes

The json parameter is ignored when either data or files is supplied. Do not pass a payload through both mechanisms and assume Requests will merge them. Decide whether the endpoint expects JSON, a form, or multipart data, then use only the matching argument.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When data= is appropriate

Use data= for a traditional form post:

import requests

form_data = {
    "username": "alice",
    "newsletter": "yes"
}

response = requests.post(
    "https://api.example.com/signup",
    data=form_data,
    timeout=10
)
response.raise_for_status()

If you intentionally pre-serialize JSON, data= can carry the resulting string, but you must manage the header yourself:

import json
import requests

payload = {"name": "Alice", "active": True}
json_text = json.dumps(payload)

response = requests.post(
    "https://api.example.com/items",
    data=json_text,
    headers={"Content-Type": "application/json"},
    timeout=10
)
response.raise_for_status()

The Requests Quickstart specifically warns that the manual data=json.dumps(...) form does not add Content-Type: application/json automatically. Omitting that header can lead to a server response such as “unsupported media type” even though the body is valid JSON.

Headers, authentication, and a production-ready function

Many APIs require authentication or an explicit Accept header. Add those headers while still using json=; do not switch to manual serialization merely because the request needs headers.

import requests


def create_item(token: str, name: str) -> dict:
    response = requests.post(
        "https://api.example.com/items",
        json={"name": name, "active": True},
        headers={
            "Authorization": f"Bearer {token}",
            "Accept": "application/json"
        },
        timeout=10
    )
    response.raise_for_status()
    return response.json()

item = create_item("YOUR_TOKEN", "Alice")
print(item)

Keep secrets out of source control and logs. Store the token in your runtime’s secret manager or environment configuration, then pass it to the function.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Check HTTP success before decoding JSON

Posting data and receiving a response are separate from proving that the operation succeeded. A server may return a JSON error document with an unsuccessful HTTP status. Call raise_for_status() before treating the response as a successful result.

response = requests.post(
    "https://api.example.com/items",
    json={"name": "Alice"},
    timeout=10
)

print(response.status_code)
response.raise_for_status()
result = response.json()

raise_for_status() raises an HTTP error for unsuccessful status codes. If you need custom handling instead, inspect response.status_code explicitly:

if 200 <= response.status_code < 300:
    print("HTTP request succeeded")
else:
    print("HTTP request failed:", response.status_code)
    print(response.text)

Do not infer success solely from the presence of a JSON object. The status code is the transport-level result; the response fields still need to satisfy your application’s business rules.

Parse response JSON defensively

response.json() decodes the response body, but it cannot decode every response. Invalid JSON raises requests.exceptions.JSONDecodeError; a 204 No Content response also has nothing to decode.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Handle an empty successful response

response = requests.post(
    "https://api.example.com/items",
    json={"name": "Alice"},
    timeout=10
)
response.raise_for_status()

if response.status_code == 204 or not response.content:
    result = None
else:
    result = response.json()

print(result)

Handle a non-JSON success response

response = requests.post(
    "https://api.example.com/items",
    json={"name": "Alice"},
    timeout=10
)
response.raise_for_status()

try:
    result = response.json()
except requests.exceptions.JSONDecodeError:
    result = response.text

print(result)

Use the response’s documented media type and status behavior when deciding whether to parse JSON. Do not hide a parsing failure by catching every exception indiscriminately.

Timeouts are part of the request design

Always set a finite timeout for production calls. Without one, a stalled connection can leave a worker waiting indefinitely. The timeout belongs on the request itself:

response = requests.post(
    "https://api.example.com/items",
    json={"name": "Alice"},
    timeout=10
)

Choose a value appropriate to the API and your application’s latency budget. A short interactive request and a long-running data import may need different limits. A timeout means your client stopped waiting; it does not prove that the server did not receive or process the request. Be careful about automatically repeating non-idempotent POST operations unless the API provides an idempotency mechanism.

Complete examples in Python, cURL, and Node.js

Python Requests

import requests

payload = {
    "name": "Alice",
    "active": True
}

response = requests.post(
    "https://api.example.com/items",
    json=payload,
    headers={"Accept": "application/json"},
    timeout=10
)
response.raise_for_status()

if response.status_code == 204 or not response.content:
    print("Created; no response body")
else:
    print(response.json())

cURL equivalent

curl --request POST "https://api.example.com/items" 
  --header "Content-Type: application/json" 
  --header "Accept: application/json" 
  --data '{"name":"Alice","active":true}'

Node.js fetch equivalent

const payload = { name: "Alice", active: true };

const response = await fetch("https://api.example.com/items", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Accept": "application/json"
  },
  body: JSON.stringify(payload)
});

if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}

const text = await response.text();
const result = text ? JSON.parse(text) : null;
console.log(result);

Unlike Requests’ json= convenience argument, the JavaScript example explicitly calls JSON.stringify() and sets the content type.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting common failures

400 Bad Request

The server received the request but rejected its contents. Compare field names, required values, data types, and nesting with the API contract. Print the response body after catching the HTTP error; many APIs put the exact validation message there.

401 Unauthorized or 403 Forbidden

Check the token, authorization scheme, required scopes, and whether the credential is being sent to the intended host. Avoid printing the full authorization header while debugging.

415 Unsupported Media Type

The endpoint does not recognize the body’s media type. For JSON, prefer json=payload. If you used data=json.dumps(payload), add Content-Type: application/json yourself.

The server says the body is form data

You probably passed a dictionary to data=. Change it to json=payload when the endpoint expects JSON. Also verify that no data or files argument is causing Requests to ignore json.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

JSONDecodeError after a successful status

The response may be empty, plain text, HTML, or malformed JSON. Check response.status_code, response.headers, and response.text before calling response.json(). Treat 204 as an intentional no-body response.

Timeout

Confirm the URL, DNS, network access, proxy settings, and server health. Increase the timeout only when the endpoint legitimately needs more time. Do not remove the timeout as a workaround.

My JSON contains a value Python cannot serialize

The object passed to json= must be JSON serializable. Convert application-specific objects, such as custom classes or date types, to strings, numbers, dictionaries, or lists before sending them.

Or skip the browser setup

If the next step in your workflow is capturing a web page or API documentation rather than posting data to an API, ScreenshotNeo provides a single-call screenshot API. It removes cookie and consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and each response identifies the page verdict and billing status in headers. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

See the ScreenshotNeo API documentation for all options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

There is a free plan with 1,000 screenshots per month and no card required. Paid plans start at $5 for 3,000 screenshots; every feature is included on every plan. Create a free ScreenshotNeo account to begin.

Bottom line

For a JSON API, use requests.post(url, json=payload, timeout=...), call raise_for_status(), and parse the body only when the response actually contains JSON. Reserve data= for forms or deliberately pre-serialized content, and remember that json is ignored whenever data or files is supplied.

Frequently Asked Questions

Does raise_for_status() read the API’s error message?

No. It evaluates the HTTP status and raises an exception for an unsuccessful response. Inspect the exception’s response, or log the response body separately, when you need the API’s validation details.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can a POST return success without a JSON object?

Yes. An API can legitimately return 204 No Content or another non-JSON body. Check the status and body length before calling response.json().

What is the safest value to use for a timeout?

There is no universal number. Set a finite value that fits the endpoint’s expected latency and your application’s deadline, then handle timeout failures explicitly.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.