1049

I need to POST a JSON from a client to a server. I'm using Python 2.7.1 and simplejson. The client is using Requests. The server is CherryPy. I can GET a hard-coded JSON from the server (code not shown), but when I try to POST a JSON to the server, I get "400 Bad Request".

Here is my client code:

data = {'sender':   'Alice',
    'receiver': 'Bob',
    'message':  'We did it!'}
data_json = simplejson.dumps(data)
payload = {'json_payload': data_json}
r = requests.post("http://localhost:8080", data=payload)

Here is the server code.

class Root(object):

    def __init__(self, content):
        self.content = content
        print self.content  # this works

    exposed = True

    def GET(self):
        cherrypy.response.headers['Content-Type'] = 'application/json'
        return simplejson.dumps(self.content)

    def POST(self):
        self.content = simplejson.loads(cherrypy.request.body.read())

Any ideas?

ivanleoncz
  • 9,070
  • 7
  • 57
  • 49
Charles R
  • 17,989
  • 6
  • 20
  • 18
  • I was using a stripped down version of an example straight out of the [documentation](http://docs.cherrypy.org/dev/progguide/REST.html). – Charles R Mar 31 '12 at 00:57
  • My comment still stands - CherryPy does not call class `__init__` methods with a `content` argument (and does not claim to in the link you supply). In the detailed example they have, the user supplies the code that calls `__init__` and provides the arguments, which we have not seen here so I have no idea what state your object is in when your `# this works` comment is relevant. – Nick Bastin Mar 31 '12 at 02:49
  • 1
    Are you asking to see the line where the instance is created? – Charles R Mar 31 '12 at 03:19
  • yeah, I was trying to start up your example in order to test it, and I wasn't sure how you were instantiating it. – Nick Bastin Mar 31 '12 at 04:02
  • The code has changed. I'm now creating it without the extra argument. `cherrypy.quickstart(Root(), '/', conf)`. – Charles R Apr 01 '12 at 05:39
  • 1
    Alternatively, If you use a tool like _Postman_ to test your API calls, you can generate code snippet. Like snippets in Python that uses the `requests` package. [Postman documentation](https://learning.getpostman.com/docs/postman/sending_api_requests/generate_code_snippets/) – amiabl Jun 07 '19 at 12:56

10 Answers10

1710

Starting with Requests version 2.4.2, you can use the json= parameter (which takes a dictionary) instead of data= (which takes a string) in the call:

>>> import requests
>>> r = requests.post('http://httpbin.org/post', json={"key": "value"})
>>> r.status_code
200
>>> r.json()
{'args': {},
 'data': '{"key": "value"}',
 'files': {},
 'form': {},
 'headers': {'Accept': '*/*',
             'Accept-Encoding': 'gzip, deflate',
             'Connection': 'close',
             'Content-Length': '16',
             'Content-Type': 'application/json',
             'Host': 'httpbin.org',
             'User-Agent': 'python-requests/2.4.3 CPython/3.4.0',
             'X-Request-Id': 'xx-xx-xx'},
 'json': {'key': 'value'},
 'origin': 'x.x.x.x',
 'url': 'http://httpbin.org/post'}
Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
Zeyang Lin
  • 17,328
  • 1
  • 13
  • 10
  • 2
    Setting this to the accepted answer since this is more idiomatic as of 2.4.2. Keep in mind, for crazy unicode, this may not work. – Charles R Sep 23 '15 at 23:00
  • 8
    I saw an example of this that took the dict object and performed json.dumps(object) before sending. Don't do this...it messes up your JSON. The above is perfect..you can pass it a python object and it turns into perfect json. – MydKnight May 15 '20 at 23:13
  • 7
    @MydKnight looking at the source code, Requests sets the content type then does [`json.dumps(your_json)`](https://github.com/psf/requests/blob/4f6c0187150af09d085c03096504934eb91c7a9e/requests/models.py#L469) and converts the result from a `str` to `bytes`. – Boris Verkhovskiy Jan 12 '21 at 18:18
  • I logged into my account especially to give you +1 for this. Thanks. I was stuck for a couple of hours until I saw you could specify json=dict It' a script for Linode DNS API that I am working on. Thank you very much! – bitofagoob Jan 30 '22 at 23:27
  • Also should note, that in order to add authentication, you can easily add the `auth=HTTPBasicAuth(user, pass)` to the params. – BogeyMan Mar 09 '22 at 00:31
  • 2
    Before I found this answer I had also to use ```data=json.dumps(...)``` but without ```Content-type: application/json``` in the header it wouldn't work. This ```json=...``` thing works perfect. – elano7 Jul 01 '22 at 08:30
  • Server sends me absolutely different results with using `data=` and `json=`. Thanks! – 555Russich Aug 10 '22 at 16:08
534

It turns out I was missing the header information. The following works:

import requests

url = "http://localhost:8080"
data = {'sender': 'Alice', 'receiver': 'Bob', 'message': 'We did it!'}
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
r = requests.post(url, data=json.dumps(data), headers=headers)
Alan W. Smith
  • 24,647
  • 4
  • 70
  • 96
Charles R
  • 17,989
  • 6
  • 20
  • 18
  • 1
    Good catch - I saw your `application/json` in `GET` and somehow missed that you hadn't provided it on the request. You may also need to make sure that you return something from `POST` or you might get a `500`. – Nick Bastin Mar 31 '12 at 04:01
  • Doesn't seem to be necessary. When I print `r`, I get ``. – Charles R Apr 01 '12 at 05:36
  • How do I retrieve this json at the server side ? – VaidAbhishek Feb 15 '13 at 12:01
  • r = requests.get('http://localhost:8080') c = r.content result = simplejson.loads(c) – Charles R May 11 '13 at 18:15
  • 7
    Little heads up before using `json.dumps` here. The `data` parameter of `requests` works fine with dictionaries. No need for converting to a string. – Advait Saravade Jul 02 '18 at 06:32
  • This was the best answer for me as requests' built in json support uses Python's built in json support which could not handle my object types. So I needed to use jsonpickle and manually set the media type to json – Geordie Jul 16 '18 at 20:58
  • yeah, json.dumps(data) is a very efficient and amazing way to do it. – Safeer Abbas Jan 08 '21 at 15:32
  • Is `headers=headers` necessary? – lolololol ol Oct 27 '22 at 17:17
  • 1
    @lolololol ol It's necessary. You are setting an argument for the function/method you're calling. In this case the function/method is named post(). The left side of the assignment operator `=` is the parameter name some dev defined (e.g. `def post(some_param0, headers, ...):`). The righthand side of the assignment operator (i.e. assignment value) is the variable *you* defined (e.g. `headers = { ... }`) that also just happens to be called `headers` in this example. You could rename the right side of the assignment (e.g. the assignment value) to whatever you like! – chevydog Jul 02 '23 at 21:50
92

From requests 2.4.2 (https://pypi.python.org/pypi/requests), the "json" parameter is supported. No need to specify "Content-Type". So the shorter version:

requests.post('http://httpbin.org/post', json={'test': 'cheers'})
ZZY
  • 3,689
  • 19
  • 22
75

Which parameter between data / json / files you need to use depends on a request header named Content-Type (you can check this through the developer tools of your browser).

When the Content-Type is application/x-www-form-urlencoded, use data=:

requests.post(url, data=json_obj)

When the Content-Type is application/json, you can either just use json= or use data= and set the Content-Type yourself:

requests.post(url, json=json_obj)
requests.post(url, data=jsonstr, headers={"Content-Type":"application/json"})

When the Content-Type is multipart/form-data, it's used to upload files, so use files=:

requests.post(url, files=xxxx)
Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
xiaoming
  • 1,069
  • 8
  • 10
  • +1. If you're using `curlify` to see the request made from the response, the content type header will not be set unless you follow these instructions. `print(curlify.to_curl(project.request))` – Olshansky Apr 12 '21 at 20:54
  • The key realization is that every parameter will perform automagic serialization without regard for the current value of the `Content-Type` header. Consequently, `data=json_obj` is basically always incorrect; the value must be stringified first, either explicitly by the caller or implicitly via the `json` parameter. – mkjeldsen Apr 22 '22 at 07:43
48

The better way is:

url = "http://xxx.xxxx.xx"
data = {
    "cardno": "6248889874650987",
    "systemIdentify": "s08",
    "sourceChannel": 12
}
resp = requests.post(url, json=data)
ivanleoncz
  • 9,070
  • 7
  • 57
  • 49
ellen
  • 547
  • 4
  • 5
11
headers = {"charset": "utf-8", "Content-Type": "application/json"}
url = 'http://localhost:PORT_NUM/FILE.php'

r = requests.post(url, json=YOUR_JSON_DATA, headers=headers)
print(r.text)
isa
  • 599
  • 7
  • 18
8

Works perfectly with python 3.5+

client:

import requests
data = {'sender':   'Alice',
    'receiver': 'Bob',
    'message':  'We did it!'}
r = requests.post("http://localhost:8080", json={'json_payload': data})

server:

class Root(object):

    def __init__(self, content):
        self.content = content
        print self.content  # this works

    exposed = True

    def GET(self):
        cherrypy.response.headers['Content-Type'] = 'application/json'
        return simplejson.dumps(self.content)

    @cherrypy.tools.json_in()
    @cherrypy.tools.json_out()
    def POST(self):
        self.content = cherrypy.request.json
        return {'status': 'success', 'message': 'updated'}
Ruhil Jaiswal
  • 135
  • 1
  • 5
  • 1
    Note/reminder: the `json` arg automatically adds the `content-type` header for `POST` in newer versions of Python. – MarkHu Feb 15 '22 at 19:59
5

With current requests you can pass in any data structure that dumps to valid JSON , with the json parameter, not just dictionaries (as falsely claimed by the answer by Zeyang Lin).

import requests
r = requests.post('http://httpbin.org/post', json=[1, 2, {"a": 3}])

this is particularly useful if you need to order elements in the response.

Ash
  • 79
  • 1
  • 6
0

I solved it this way:

from flask import Flask, request
from flask_restful import Resource, Api


req = request.json
if not req :
    req = request.form
req['value']
betelgeuse
  • 1,136
  • 3
  • 13
  • 25
Birk Net
  • 17
  • 2
-1

It always recommended that we need to have the ability to read the JSON file and parse an object as a request body. We are not going to parse the raw data in the request so the following method will help you to resolve it.

def POST_request():
    with open("FILE PATH", "r") as data:
        JSON_Body = data.read()
    response = requests.post(url="URL", data=JSON_Body)
    assert response.status_code == 200
Pushparaj
  • 92
  • 4