Python: FastAPI error 422 with POST request when sending JSON data

I’m building a simple API to test a database. When I use GET request everything works fine, but if I change to POST, I get 422 Unprocessable Entity error.

Here is the FastAPI code:

from fastapi import FastAPI

app = FastAPI()

@app.post("/")
def main(user):
    return user

Then, my request using JavaScript

let axios = require('axios')

data = { 
    user: 'smith' 
}

axios.post('http://localhost:8000', data)
    .then(response => (console.log(response.url)))

Also, using Python requests:

import requests

url="http://127.0.0.1:8000"
data = {'user': 'Smith'}

response = requests.post(url, json=data)
print(response.text)

I also tried to parse as JSON, enconding using utf-8, and change the headers, but nothing worked for me.

Straight from the documentation:

The function parameters will be recognized as follows:

  • If the parameter is also declared in the path, it will be used as a path parameter.
  • If the parameter is of a singular type (like int, float, str, bool, etc) it will be interpreted as a query parameter.
  • If the parameter is declared to be of the type of a Pydantic model, it will be interpreted as a request body.”

So to create a POST endpoint that receives a body with a user field you would do something like:

from fastapi import FastAPI
from pydantic import BaseModel


app = FastAPI()


class Data(BaseModel):
    user: str


@app.post("https://stackoverflow.com/")
def main(data: Data):
    return data

A response having a 422 (unprocessable entity) status code will have a response body that specifies the error message, telling exactly which part of your request is missing or doesn’t match the expected format. The code snippet you povided shows that you are trying to post JSON data to an endpoint that is expecting user being a query parameter, rather than JSON payload. Hence, the 422 unprocessable entity error. Below are given four different options on how to define an endpoint to expect JSON data.

Option 1

As per the documentation, when you need to send JSON data from a client (let’s say, a browser) to your API, you send it as a request body (through a POST request). To declare a request body, you can use Pydantic models.

from pydantic import BaseModel

class User(BaseModel):
    user: str

@app.post("https://stackoverflow.com/")
def main(user: User):
    return user

Option 2

If one doesn’t want to use Pydantic models, they could also use Body parameters. If a single body parameter is used (as in your example), you can use the special Body parameter embed.

from fastapi import Body

@app.post("https://stackoverflow.com/")
def main(user: str = Body(..., embed=True)):
    return {'user': user}

Option 3

Another (less recommended) way would be to use a Dict type (or simply dict in Python 3.9+) to declare a key:value pair. However, in this way, you can’t use custom validations for various attributes in your expected JSON, as you would do with Pydantic models or Body fields (e.g., check if an email address is valid, or if a string follows a specific pattern).

from typing import Dict, Any

@app.post("https://stackoverflow.com/")
def main(payload: Dict[Any, Any]): 
    return payload

In the example above, payload could also be defined as payload: dict[Any, Any], or simply payload: dict.

Option 4

If you are confident that the incoming data is a valid JSON, you can use Starlette’s Request object directly to get the request body parsed as JSON, using await request.json(). However, with this approach, not only can’t you use custom validations for your attributes, but you would also need to define your endpoint with async def, since request.json() is an async method and thus, one needs to await it (have a look at this answer for more details on def vs async def).

from fastapi import Request

@app.post("https://stackoverflow.com/")
async def main(request: Request): 
    return await request.json()

If you wish, you could also implement some checking on the Content-Type request header value, before attempting to parse the data, similar to this answer. However, just because a request says application/json in the Content-Type header, it doesn’t always mean that this is true, or that the incoming data is a valid JSON (i.e., may be missing a curly bracket, have a key that does not have a value, etc). Hence, you could use a try-except block when you attempt to parse the data, letting you handle any JSONDecodeError, in case there is an issue with the way in which your JSON data is formatted.

from fastapi import Request, HTTPException
from json import JSONDecodeError

@app.post("https://stackoverflow.com/")
async def main(request: Request):
    content_type = request.headers.get('Content-Type')
    
    if content_type is None:
        raise HTTPException(status_code=400, detail="No Content-Type provided")
    elif content_type == 'application/json':
        try:
            return await request.json()
        except JSONDecodeError:
            raise HTTPException(status_code=400, detail="Invalid JSON data")
    else:
        raise HTTPException(status_code=400, detail="Content-Type not supported")

Test the above options

Using Python requests library

Related answer can be found here.

import requests

url="http://127.0.0.1:8000/"
payload ={'user': 'foo'}
resp = requests.post(url=url, json=payload)
print(resp.json())

Using JavaScript Fetch API

Related answers can be found here and here as well. For examples using axios, please have a look at this answer, as well as this answer and this answer.

fetch("https://stackoverflow.com/", {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({'user': 'foo'})
    })
    .then(resp => resp.json()) // or, resp.text(), etc
    .then(data => {
        console.log(data); // handle response data
    })
    .catch(error => {
        console.error(error);
    });

In my case, I was calling the python API from different python project like this

queryResponse = requests.post(URL, data= query)

I was using the data property, I changed it to json, then it worked for me

queryResponse = requests.post(URL, json = query)

If you’re using the fetch API and still getting the 422 Unprocessable Entity, ensure that you have set the Content-Type header:

fetch(someURL, {
  method: "POST",
  headers: {
    "Content-type": "application/json"
  },
  body
}).then(...)

This solved the issue in my case. On the server-side I’m using Pydantic models, so if you aren’t using those, see the above answers.

Leave a Comment