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

Use Requests’ json= parameter: pass a Python dictionary or list, set a finite timeout, check the HTTP status, then parse the response. A reliable minimal pattern is requests.post(url, json=payload, timeout=10) followed by response.raise_for_status() and response.json().

The recommended pattern

For an API that expects a JSON request body, pass the Python object through json=. Requests serializes the object for you and uses the JSON request workflow.

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 example does four separate jobs:

  • payload is a normal JSON-serializable Python object.
  • json=payload creates the request body using JSON encoding.
  • timeout=10 prevents the call from waiting indefinitely.
  • raise_for_status() rejects unsuccessful HTTP responses before the response is treated as a success.

The current Requests documentation identifies version 2.34.2 and lists official support for Python 3.10 and newer (documentation accessed in 2026). Check your own environment if you maintain an older Python runtime or a pinned dependency set.

json= versus data= and files=

These parameters describe different body formats. Choosing the wrong one is a common reason an API reports a missing field or an unsupported content type.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Goal Requests call What is sent
JSON API body requests.post(url, json=payload) Requests serializes the object through its JSON workflow.
Form submission requests.post(url, data=form_data) A dictionary is form-encoded.
Multipart upload requests.post(url, files=files) Requests builds a multipart body for files and fields.
Already serialized body requests.post(url, data=json_text) You provide the exact text and are responsible for the appropriate headers.

Do not provide competing body mechanisms accidentally. Requests ignores json= when either data or files is supplied. If you pass both while expecting JSON, the value in json= will not be used.

Why manual json.dumps() can produce the wrong header

This code serializes the object 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,
    timeout=10,
)

The body is now a string, but this form does not add Content-Type: application/json automatically. An API that uses the content type to select its parser may reject it or interpret it as an unknown body format.

If you deliberately pre-serialize the body, set the header yourself:

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

For ordinary JSON APIs, json=payload is shorter and avoids this header trap. Use manual serialization only when you need control over the exact serialized text or are integrating with a service that requires a special encoding process.

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.

Building a production-ready JSON POST

Send authentication and other headers

Authentication is independent of JSON encoding. Add the headers required by the API while keeping the body in json=.

import requests

url = "https://api.example.com/items"
payload = {"name": "Alice", "active": True}
headers = {
    "Authorization": "Bearer YOUR_TOKEN",
    "Accept": "application/json",
}

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

Do not put a secret token inside the JSON object unless the API explicitly defines it as a body field. Keep credentials in environment variables or your application’s secret store.

Use a session for repeated calls

A Session lets related requests share configuration such as headers and authentication.

import os
import requests

payload = {"name": "Alice", "active": True}
headers = {
    "Authorization": f"Bearer {os.environ['API_TOKEN']}",
    "Accept": "application/json",
}

with requests.Session() as session:
    session.headers.update(headers)
    response = session.post(
        "https://api.example.com/items",
        json=payload,
        timeout=10,
    )
    response.raise_for_status()
    result = response.json()

print(result)

Send nested objects and arrays

JSON can contain nested dictionaries and lists as long as every value is JSON serializable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
payload = {
    "customer": {
        "name": "Alice",
        "contact": {"email": "alice@example.com"},
    },
    "tags": ["new", "priority"],
    "active": True,
}

response = requests.post(
    "https://api.example.com/customers",
    json=payload,
    timeout=10,
)
response.raise_for_status()

Python booleans and None are converted to JSON true, false, and null. Values such as an open file object, a set, or a custom class are not automatically JSON serializable; convert them to strings, lists, numbers, dictionaries, or null-compatible values first.

Checking whether the API call succeeded

Check HTTP status before treating the result as success

A server can return a JSON error document with an unsuccessful HTTP status. Parsing that document does not make the request successful.

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

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

raise_for_status() raises a Requests HTTP error for unsuccessful responses. If you need custom handling, inspect response.status_code instead:

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

Use the status code defined by the API contract. A successful creation is often represented by a 2xx status, but the exact code and response fields belong to the service you are calling.

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

Parse JSON defensively

response.json() decodes a JSON response. It raises requests.exceptions.JSONDecodeError when the response is not valid JSON, including a response with no body such as HTTP 204 No Content.

import requests

try:
    response = requests.post(
        "https://api.example.com/items",
        json={"name": "Alice"},
        timeout=10,
    )
    response.raise_for_status()
except requests.exceptions.Timeout:
    print("The server did not respond before the timeout")
except requests.exceptions.RequestException as exc:
    print(f"HTTP or network failure: {exc}")
else:
    if response.status_code == 204 or not response.content:
        result = None
    else:
        try:
            result = response.json()
        except requests.exceptions.JSONDecodeError:
            print("The server returned a non-JSON success response")
            print(response.text)
        else:
            print(result)

Only parse JSON when the endpoint promises a JSON response and the response has content. For diagnostics, response.text gives decoded text and response.content gives the raw response bytes.

Timeouts, retries and safe failure handling

Always choose a finite timeout appropriate to the API. A timeout is not a guarantee that the server stopped processing your request; it only limits how long your client waits. For a POST that may create a record, retrying blindly can create duplicates if the first request reached the server but the response was lost.

  • Use an idempotency key when the API supports one for create or payment operations.
  • Retry only when the API documents a safe policy and the failure is plausibly transient.
  • Log the status code and a request identifier, but never log access tokens or sensitive payload fields.
  • Keep the original payload available when you need to inspect or replay a failed request.

Requests exposes timeout and other request options directly. Set the timeout on every call or on a wrapper used by your application rather than relying on an indefinite default.

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

Equivalent requests with cURL and Node.js

When debugging an API, reproducing the same JSON body in another client helps distinguish a Python issue from a server or credential issue.

cURL

curl -X POST "https://api.example.com/items" 
  -H "Content-Type: application/json" 
  -H "Accept: application/json" 
  -H "Authorization: Bearer YOUR_TOKEN" 
  --data '{"name":"Alice","active":true}'

Node.js using built-in fetch

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

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

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

const result = res.status === 204 ? null : await res.json();
console.log(result);

Unlike Requests’ json= convenience parameter, JavaScript’s fetch requires you to call JSON.stringify() and set the content type explicitly.

Common errors and fixes

“The API says the body is missing”

Confirm that you used json=payload, not a misspelled argument, and that another data or files argument is not overriding it. Print a non-secret representation of the payload before sending.

“Unsupported media type” or “expected application/json”

You may have supplied a serialized string through data= without a content-type header. Switch to json=payload, or add Content-Type: application/json when manual serialization is intentional.

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, may be HTTP 204, or may contain HTML or plain text rather than JSON. Check response.status_code, response.headers, and response.text before calling response.json().

TypeError: Object of type ... is not JSON serializable

Convert the unsupported value to a JSON-compatible representation. For example, convert a set to a list and a date or decimal to the string or number format required by the API.

The request hangs or fails intermittently

Add a finite timeout, verify DNS and network access, and inspect whether the failure is a connection error or an HTTP error. A timeout exception means the client did not receive a response within the configured interval; it does not identify whether the server completed the operation.

The server returns 401, 403 or 422

  • 401: check the token, its expiration and the required authentication scheme.
  • 403: the identity may be valid but lack permission, or the API may restrict the resource.
  • 422: the JSON was understood but failed validation; inspect the error fields and compare names, types and required values with the API contract.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If the task behind your integration is obtaining a clean screenshot of a website rather than implementing browser automation yourself, ScreenshotNeo provides a single HTTP request. Its API accepts a URL and returns PNG, JPEG, WebP or PDF output. The request can be made from Python, cURL or Node.js just like any other HTTP endpoint.

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

cURL

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

Python

import requests

r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Before capture, ScreenshotNeo accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.

The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; other listed plans are Growth ($15 for 15,000), Pro ($39 for 60,000), Scale ($99 for 250,000) and Business ($249 for 1,000,000). Yearly billing gives two months free, and every feature is available on every plan. See the ScreenshotNeo API documentation for request options and response behavior, then sign up for the free plan.

Request checklist

  • Confirm the endpoint expects JSON rather than a form or multipart body.
  • Pass a dictionary or list with json=payload.
  • Do not combine json= with data= or files= unintentionally.
  • Set authentication and any required headers separately.
  • Use a finite timeout.
  • Call raise_for_status() or handle status_code before parsing.
  • Handle empty or non-JSON responses before calling response.json().
  • Protect secrets and avoid unsafe automatic retries for non-idempotent operations.

Frequently Asked Questions

Can a JSON POST return no body even when it succeeds?

Yes. An API may report success with HTTP 204 No Content. Check the status and response content before calling response.json().

What should I preserve when investigating a failed request?

Keep the endpoint, status code, non-sensitive response text, timeout setting and a redacted copy of the payload. Never include access tokens or confidential fields in logs.

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

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.