Sending JSON, forms and other bodies

Requests · Essentials · 10 min

Creating or updating resources through an API means sending a body in the format the server expects.

Requests offers three ways to send a body, and each sets the right Content-Type for you:

  • json=obj serialises a dict or list to JSON and sets Content-Type: application/json. This is what most modern REST APIs expect.
  • data=dict sends an HTML-form body, application/x-www-form-urlencoded, the way a browser submits a simple form.
  • data=str_or_bytes sends the content as-is with no Content-Type added. Use it for XML, plain text or a pre-serialised payload, and set the header yourself.
  • files={"field": file_obj} sends multipart/form-data, used for uploads (covered in a later lesson).

The methods are the usual HTTP verbs: post to create, put to replace, patch to update partially, delete to remove. They all accept the same keyword arguments as get.

Two classic mistakes:

  • Using data= when the API wants JSON. data={"a": 1} sends a=1 as a form, and a JSON API will reply with 400 or 415. Use json=.
  • Passing both json= and data=: when data is given, json is ignored.

The response to a create call is often 201 Created with the new resource in the body and its URL in the Location header. Some APIs reply 204 No Content, where r.json() would fail because the body is empty.

Example

import requests

base = "https://httpbin.org"

r = requests.post(f"{base}/post", json={"name": "Ada", "tags": ["math"]}, timeout=10)
echo = r.json()
print(echo["headers"]["Content-Type"])   # application/json
print(echo["json"])                      # {'name': 'Ada', 'tags': ['math']}

r = requests.post(f"{base}/post", data={"name": "Ada", "lang": "en"}, timeout=10)
echo = r.json()
print(echo["headers"]["Content-Type"])   # application/x-www-form-urlencoded
print(echo["form"])                      # {'lang': 'en', 'name': 'Ada'}

xml = "<user><name>Ada</name></user>"
r = requests.put(f"{base}/put", data=xml,
                 headers={"Content-Type": "application/xml"}, timeout=10)
print(r.json()["data"])                  # <user><name>Ada</name></user>
print(requests.delete(f"{base}/delete", timeout=10).status_code)  # 200

Remember

  • json= for JSON APIs, data= for forms or raw bodies, files= for uploads
  • Each sets Content-Type, except raw data= strings and bytes
  • post, put, patch and delete take the same arguments as get

Watch out for

  • Sending data={'a': 1} to a JSON API and getting a 400 or 415 back.

Check yourself

  1. You call requests.post(url, data={'a': 1}). What Content-Type does the request have?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: requests.readthedocs.io/en/latest/user/quickstart/#more-complicated-post-requests