Search the courses, subjects, tracks, games and features, or jump straight to a page.
Learn it your way
A short class with Sarah and her students. The other ways are under More.
Sign up free to listen. Transcripts are free to read.
So far we've only read data with GET. What if our app needs to create something on the server, like a new blog post?
Then we need a request that carries data. Is that POST?
Yes. A POST request carries the data in its body, usually as JSON, and asks the server to create or process it.
How do we know it worked?
Look at the status code. A successful creation usually comes back as 201 Created, often with the new object, and its new id, in the body.
requests.post() gives us two ways to send a body: json= and data=. What's the difference?
json= sends JSON, and data= sends form data?
Right. json= serializes your dictionary and sets the Content-Type header to application/json. data= with a dictionary sends form-encoded text, like an HTML form.
Could I just write data=json.dumps(payload)?
You could, but then you must set the Content-Type header yourself. json= does both jobs for you.
If a GET times out, we can simply send it again. Is that safe for a POST?
Maybe not. If the server created the post before the reply got lost, sending it again makes a second one.
Exactly: POST is not idempotent. So set a timeout, call raise_for_status(), and think before you retry.
Can an API protect itself against duplicates?
Some APIs, payment APIs for example, accept an idempotency key header. If a retry carries the same key, the server knows it is the same request and does not create it twice.
Sends data in the request body to create a resource or submit information; it changes server state.
json=payload serializes a dictionary to JSON and sets Content-Type: application/json.
data=payload with a dictionary sends form-encoded data, as an HTML form does.
The usual success code for a POST that created something, often with the new object in the reply.
Repeating a POST can create duplicates, so retries need care.
Building a JSON POST request with the standard library's urllib.request (it is only built here, because sending it needs a network). Note that urllib stores header names as Content-type:
import json
from urllib.request import Request
payload = {"title": "foo", "body": "bar", "userId": 1}
body = json.dumps(payload).encode("utf-8")
req = Request(
"https://jsonplaceholder.typicode.com/posts",
data=body,
headers={"Content-Type": "application/json"},
method="POST",
)
print(req.get_method(), req.full_url)
print(req.header_items())
print(req.data.decode("utf-8"))What json= and data= each put in the request body, reproduced with json.dumps and urlencode:
import json
from urllib.parse import urlencode
payload = {"title": "New Post", "body": "Example content", "userId": 2}
print("json= sends:", json.dumps(payload))
print(" Content-Type: application/json")
print("data= sends:", urlencode(payload))
print(" Content-Type: application/x-www-form-urlencoded")A simulated POST endpoint that stores each payload and replies 201 Created, which also shows why POST is not idempotent:
import json as jsonlib
STORE = [] # what the simulated server has saved
class SimulatedResponse:
def __init__(self, status_code, text):
self.status_code = status_code
self.text = text
def json(self):
return jsonlib.loads(self.text)
def simulated_post(url, json=None, timeout=None):
# The server keeps a copy of the payload and gives it a new id
created = dict(json, id=len(STORE) + 101)
STORE.append(created)
return SimulatedResponse(201, jsonlib.dumps(created))
payload = {"title": "foo", "body": "bar", "userId": 1}
response = simulated_post("https://jsonplaceholder.typicode.com/posts", json=payload)
print(response.status_code)
print(response.json())
# Sending the same request again creates a second resource
response = simulated_post("https://jsonplaceholder.typicode.com/posts", json=payload)
print("new id:", response.json()["id"], "| resources stored:", len(STORE))GET to read and POST to write; two-oh-one means it went right.
Sam's sign-up form created two accounts whenever the network was slow, because his code retried every POST that timed out. He learned that POST is not idempotent and now checks whether the account exists before retrying.
"JSON in, 201 out": send with json=, expect 201 Created back.
P.O.S.T. - Put data in the body, Over to the server, Status 201, Take care with retries.
Term
What is a POST request used for?
Click to flip
Term
Which requests argument sends a dictionary as JSON and sets the Content-Type for you?
Click to flip
Term
What does `data=` with a dictionary send?
Click to flip
Term
Which status code usually means a POST created a resource?
Click to flip
Term
Why can retrying a POST be risky?
Click to flip
An HTTP request that sends data in its body, usually to create a resource or submit information.
The data a request carries after its headers, such as a JSON document or form fields.
The data you send in a request body, often built as a Python dictionary.
Key and value pairs written as key=value&key2=value2, with the Content-Type application/x-www-form-urlencoded.
A header that tells the server the format of the body, such as application/json.
The status code a server returns when a request has created a new resource.
Describes a request that has the same effect however many times it is sent; POST is not idempotent.
A POST request is used to send data to a server. Use requests.post() in Python to send JSON or form data to REST APIs for creating or modifying resources.
The POST method is used to create a new resource or submit data to a server. In Python, the requests.post() method allows you to send data in the body of the request, often as JSON. This is useful for submitting forms, creating user accounts, or sending data to APIs. A successful POST often returns status code 201 Created.
Where a GET request reads data, a POST request writes it: the client puts data in the request body and asks the server to create or process something with it. This lesson shows requests.post() with a JSON payload, explains the difference between its json and data arguments, and covers error handling, using the free JSONPlaceholder test API.
Example: create a post. The requests code in this lesson needs the requests package (pip install requests) and network access, so it will not run in the in-browser editor. The Examples section below has standard-library versions that run anywhere.
# Needs the requests package (pip install requests) and network access
import requests
payload = {"title": "foo", "body": "bar", "userId": 1}
response = requests.post("https://jsonplaceholder.typicode.com/posts", json=payload)
print(response.status_code) # 201
print(response.json())JSONPlaceholder is a fake API: it answers as if the post had been created, returning status 201 and the post with a new id, but it does not really store anything.
The call looks like this: requests.post(url, data=None, json=None, headers=None, timeout=None)
Pass either data or json, not both: if both are given, requests sends data and ignores json. With json=payload the body is {"title": "foo", ...} and the Content-Type header is application/json. With data=payload the body is title=foo&body=bar&userId=1, exactly what an HTML form submits.
# Needs the requests package (pip install requests) and network access
import requests
new_post = {"title": "New Post", "body": "Example content", "userId": 2}
response = requests.post("https://jsonplaceholder.typicode.com/posts", json=new_post)
print(response.status_code) # Should be 201
print(response.json()) # See created objectThe reply echoes the created object, including the id the server assigned, so later requests can refer to the new resource.
Wrap the call in try and except, and define url and data first so the snippet runs as a complete program:
# Needs the requests package (pip install requests) and network access
import requests
url = "https://jsonplaceholder.typicode.com/posts"
data = {"title": "New Post", "body": "Example content", "userId": 2}
try:
response = requests.post(url, json=data, timeout=10)
response.raise_for_status()
print("Created:", response.status_code)
except requests.exceptions.RequestException as e:
print("Request failed:", e)raise_for_status() turns a 4xx or 5xx reply (for example 400 Bad Request when the payload is invalid) into an HTTPError, and RequestException catches that along with timeouts and connection errors. Be careful with automatic retries: if a POST times out, the server may already have created the resource, and sending it again can create a duplicate.