Send a JSON POST Request with Python requests

requests.post(url, json=payload) sends a JSON body to a server in a single line. The json= argument serializes your dictionary, sets the Content-Type: application/json header, and writes the request body for you. The reply returns a status code and, from most APIs, the data the server stored, which lets you confirm the call did what you intended. This is the pattern behind REST API writes, webhooks, and any endpoint that expects a JSON payload.

Python requests.post Example for JSON

Output:

Output will appear here...

Output:

Status: 201
Server stored: {'language': 'Python', 'stars': 3, 'id': 101}

How This Example Works

  1. json= serializes the dict and sets Content-Type: application/json, so the server receives a JSON object instead of form fields.
  2. timeout=10 caps the wait, so an unreachable endpoint fails fast rather than hanging the run.
  3. raise_for_status() converts a 4xx or 5xx reply into an exception, so a failed response never flows silently into the next line.
  4. response.json() returns the reply as a Python dict. This test API echoes your fields and adds a server-assigned id, and the 201 Created status confirms it treated the call as a new resource.

Verify What the Server Actually Received

Two independent signals confirm the POST worked, and a reliable check uses both. The reply body proves the content arrived: echoed fields plus a new id mean the server parsed your JSON. Run Details proves the transport: it records the request the browser actually sent.

Run Details signalWhat it confirms
One POST to jsonplaceholder.typicode.com, status 201The call left as a POST and the server created a resource
The method column reads POST, not GETrequests.post sent the verb you intended
The request duration in msRound-trip time, measured separately from JSON parsing
Status is blocked or failed, with no HTTP codeThe request never reached the server (see CORS below)
Packages lists requestsweb.run loaded the bundled requests package on import

When the status code reads 2xx but the echoed body is missing your fields, the transport succeeded and the content did not. That split is the failure most POST tutorials cannot show, because they never put the sent request and the server’s reply side by side.

json vs data: The Silent Data-Loss Bug

Send the same payload with data= instead of json= and the server still answers 2xx, yet the echoed body drops your fields. The request was fine; the encoding was wrong.

ArgumentWhat requests sendsWhat the server parses
json={"stars": 3}JSON body and Content-Type: application/jsona JSON object
data={"stars": 3}form-encoded body and application/x-www-form-urlencodedform fields, not JSON
data=json.dumps({"stars": 3})a raw JSON string with no JSON headertext it may refuse to parse as JSON

Rule of thumb: pass a dict to json= for any API that expects a JSON body, and use data= only when posting an HTML form. A JSON API that receives form encoding usually returns 2xx and quietly discards the data, which is why checking the status code is never enough.

When a Browser POST Is Blocked, Not Rejected

A cross-origin POST behaves differently in the browser than in a server script. Because the body is application/json, the browser sends a CORS preflight OPTIONS before the POST. If the endpoint does not return the matching Access-Control-Allow-Origin and Access-Control-Allow-Headers, the browser blocks the POST before it leaves.

When that happens, requests raises a connection error such as requests.exceptions.ConnectionError, not an HTTPError, and Run Details marks the request blocked or failed with no HTTP status. A 403 or 422 is the opposite signal: the request reached the server, which chose to reject it.

Rule: no HTTP status means a network or CORS problem before the server; an HTTP status means the server answered, even if it answered with an error. jsonplaceholder is CORS-enabled, so the example sends cleanly. Many public APIs are not, which is why identical requests.post code can succeed in a terminal yet fail in this runtime or any in-browser Python.

Common Mistakes

Mistake: sending a dict with data= when the API expects JSON.

Wrong:

requests.post(url, data={"stars": 3})

Right:

requests.post(url, json={"stars": 3})

Why it happens: data= form-encodes the dict and sets a form Content-Type, so a JSON API receives no JSON object and your fields are dropped from the result.

Mistake: serializing the body twice.

Wrong:

requests.post(url, json=json.dumps({"stars": 3}))

Right:

requests.post(url, json={"stars": 3})

Why it happens: json= already serializes the value. Passing a pre-built string double-encodes it into a quoted JSON string instead of an object.

Sending Headers and Auth With the POST

Most write APIs require authentication, which travels in the request headers alongside the body:

response = requests.post(
    url,
    json=payload,
    headers={"Authorization": "Bearer TOKEN"},
    timeout=10,
)

requests merges your headers with the Content-Type it sets for json=, so you supply only what is specific to the API. Never hard-code a real token in shared code; read it from an environment variable. Note that a custom header like Authorization is itself enough to trigger the CORS preflight described above. For the matching read side, see reading JSON from a URL, and wrap the call in try/except to handle connection errors.

FAQ

How do you send a POST request in Python?

Call requests.post(url, json=payload) with a dictionary as the payload. The json= argument serializes the dict, sets the Content-Type: application/json header, and sends it in one call. Read the reply with response.json().

Why does my POST return 200 but the server ignores the data?

The body was sent in the wrong format, usually as form fields through data= instead of JSON through json=. The server answers 2xx for the request itself but never parses a JSON object. Switch to json= and confirm the server echoed your fields back.

Why is my requests POST blocked in the browser?

A cross-origin JSON POST triggers a CORS preflight. If the endpoint does not return Access-Control-Allow-Origin and Access-Control-Allow-Headers, the browser blocks the request before it leaves, so requests raises a connection error with no HTTP status rather than a 4xx. The same code can still work from a terminal.

What is the difference between json and data in requests.post?

json= serializes a dict to a JSON body and sets Content-Type: application/json. data= sends form-encoded fields or a raw string without the JSON header. Use json= for JSON APIs and data= for HTML form submissions.