LESSON 158 · PYTHON TOOLS30 min

FastAPI request body: validate JSON with a Pydantic model

Declare a POST request body, separate it from path parameters, and understand automatic 422 validation responses.

Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons

Video lesson: FastAPI request body: validate JSON with a Pydantic model

The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube

Read the video transcript

Select a timestamp to watch that moment on YouTube.

  1. 00:00

    A client posts JSON to /items/, but the quantity field is missing. What should the server do? We want validation to stop the request before application code assumes the field exists. In FastAPI, a parameter annotated with a Pydantic model describes a request body. We will work through the data shape, the route declaration, and actual status codes in a local test client. This server code runs in a normal Python environment, not in the browser console on this page. A FastAPI request body is JSON sent by the client, commonly with POST or PUT. Define a Pydantic BaseModel with typed fields, then annotate the route function

  2. 00:42

    parameter with that model. FastAPI reads the JSON body, validates it, and passes a model instance into your function. This is distinct from a path parameter named in the URL and from a simple query parameter. The complete local app below uses a POST /items/ route; it must be run in a local Python environment rather than the browser console because the console cannot host an HTTP server. A client sends a JSON document in the request body. In a local FastAPI app, a Pydantic BaseModel makes the expected fields visible as Python annotations and an OpenAPI schema.

  3. 01:26

    Add constraints such as minimum quantity only after deciding what the API promises. A missing required field should cause a validation response rather than reaching a route that immediately raises KeyError. The broken example is a plain dictionary access. It raises KeyError when quantity is absent. An API that waits until this line to discover a missing required field gives a less useful failure and may leave business logic half executed. The key lesson is to describe the incoming contract at the HTTP boundary. That is where a framework can reject malformed requests consistently and produce field-specific errors for callers.

  4. 02:09

    For the example, an item has a nonempty name and a quantity of at least one. A missing quantity, wrong JSON shape, or quantity below one produces a 422 validation response before the route function runs. That is a useful contract, but it is not business validation for every possible rule. Consider whether strings should be coerced to numbers, whether extra fields should be accepted, and whether whitespace-only names should be rejected. Make these choices explicit in the model rather than assuming a plain `int` annotation captures every requirement. The browser example here uses a Python dictionary to represent a decoded JSON

  5. 02:50

    object. It does not start FastAPI; use the local- only code block above to run the actual server. Focus first on the contract: `name` must contain visible text and `quantity` must be a positive integer for this example. The browser task makes those conditions explicit. The diagnosis gives the same body to a Pydantic model and prints the error location. The model knows quantity is required and reports its absence. A field annotation is not merely documentation: FastAPI uses this model to parse JSON and validate before the route function runs.

  6. 03:32

    A path parameter comes from a named URL segment, and a simple optional parameter may come from a query string; they are different sources. The body model says where these fields belong. The browser exercises use local dictionaries to let you reason about body validation without a network call. The function `normalize_item` checks a JSON-like Python dict, rejects bool as a quantity even though bool subclasses int, strips the name, and returns a narrow result. This does not replace Pydantic or imitate every FastAPI rule. It lets you test the same application- level contract before connecting it to the route.

  7. 04:13

    In the local app, the route receives the Pydantic model and returns the cleaned fields. In a FastAPI route such as PUT /items/{item_id}, `item_id` comes from the path, while a parameter annotated as a Pydantic model comes from the JSON body. A simple string parameter that is not in the path is commonly a query parameter. This distinction helps explain why putting JSON fields in the wrong place does not satisfy the request body model. The corrected code declares a POST route and an Item model with a minimum quantity. The printed model data demonstrates that valid input passes validation.

  8. 04:54

    The route trims the name and returns only the fields it means to expose. For a production API, decide whether whitespace-only names should be rejected and whether coercion is acceptable; the browser practice implements a stricter application policy and makes boolean quantity invalid. The two snippets serve different purposes but share an explicit contract. Save the local example as app.py and run it with `fastapi dev app.py` after installing fastapi[standard]. Send a POST with Content-Type application/json to /items/ or use /docs. Try a valid body and one missing quantity, then inspect the 422 detail to see which

  9. 05:35

    field failed. Do not place a body on a GET route for this basic API design. The prior HTTP response lesson validates incoming server data on the client side; this page covers validation of a client's request arriving at a FastAPI server. For the simpler POST /items/ tutorial, there is no path identifier, only the Item body. Test it in the automatically generated /docs UI or with an HTTP client. Sending an object with both required fields should reach the function. Missing quantity should yield 422 with a field location in the response details.

  10. 06:18

    Do not claim that a browser-only Python function is an HTTP server. The final demo uses FastAPI TestClient to send a valid JSON body and an invalid one without opening a public server. One gets 200, the missing quantity gets 422 and identifies body.quantity. If your installed dependencies differ, run the local setup described on the page. Use /docs when running the app to inspect the generated schema. A model helps the HTTP layer, while separate business rules may still be needed after validation. Do not confuse checking an incoming request body with validating a remote

  11. 07:00

    server response, which the linked earlier lesson covers. A model can enforce syntax and constraints before business logic runs, but the exact coercion policy deserves care. In plain Python, bool is a subclass of int, so `isinstance(True, int)` is true. If an API must accept only literal integer quantities, configure strict validation. The browser task uses exact type checks to make this policy easy to observe. After normalization, return only the fields the route needs. Strip surrounding whitespace from the name and reject an all-whitespace name. In a production API, tests should submit both a valid JSON body and several invalid

  12. 07:44

    bodies through the HTTP layer, then assert status codes and field locations. The local app block is the starting point; the browser exercise checks the decision logic independently.

Understand the concept

A FastAPI request body is JSON sent by the client, commonly with POST or PUT. Define a Pydantic BaseModel with typed fields, then annotate the route function parameter with that model. FastAPI reads the JSON body, validates it, and passes a model instance into your function. This is distinct from a path parameter named in the URL and from a simple query parameter. The complete local app below uses a POST /items/ route; it must be run in a local Python environment rather than the browser console because the console cannot host an HTTP server.

For the example, an item has a nonempty name and a quantity of at least one. A missing quantity, wrong JSON shape, or quantity below one produces a 422 validation response before the route function runs. That is a useful contract, but it is not business validation for every possible rule. Consider whether strings should be coerced to numbers, whether extra fields should be accepted, and whether whitespace-only names should be rejected. Make these choices explicit in the model rather than assuming a plain `int` annotation captures every requirement.

The browser exercises use local dictionaries to let you reason about body validation without a network call. The function `normalize_item` checks a JSON-like Python dict, rejects bool as a quantity even though bool subclasses int, strips the name, and returns a narrow result. This does not replace Pydantic or imitate every FastAPI rule. It lets you test the same application-level contract before connecting it to the route. In the local app, the route receives the Pydantic model and returns the cleaned fields.

Save the local example as app.py and run it with `fastapi dev app.py` after installing fastapi[standard]. Send a POST with Content-Type application/json to /items/ or use /docs. Try a valid body and one missing quantity, then inspect the 422 detail to see which field failed. Do not place a body on a GET route for this basic API design. The prior HTTP response lesson validates incoming server data on the client side; this page covers validation of a client's request arriving at a FastAPI server.

  • BaseModel declares request body fields
  • Path, query, and body parameters are different
  • Invalid bodies get a 422 response
  • The route runs only after request validation

See it step by step

Read the code, predict the output, then compare it with the result.

01. Identify a JSON-shaped body

PYTHON
body = {'name':'Pencils','quantity':3}
print(type(body).__name__, sorted(body))
EXPECTED OUTPUT
dict ['name', 'quantity']

JSON objects arrive conceptually as key/value records; the model then validates fields and types.

02. Separate missing from present fields

PYTHON
body = {'name':'Pencils'}
print('quantity' in body)
print(body.get('quantity'))
EXPECTED OUTPUT
False
None

A required model field cannot simply be assumed present, and a missing value is not the same as a valid zero.

03. Check the application-level quantity

PYTHON
for value in (3, 0, True):
    print(value, type(value) is int and value >= 1)
EXPECTED OUTPUT
3 True
0 False
True False

A handwritten strict quantity check excludes bool as well as zero; model configuration should state comparable rules.

Run the complete example locally

Save this as app.py in a local Python environment. Install with python -m pip install 'fastapi[standard]', then run python app.py to see both response codes. To use the interactive API docs, run fastapi dev app.py and open /docs. A FastAPI server cannot run in this page's browser console.

PYTHON
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field

app = FastAPI()

class Item(BaseModel):
    name: str = Field(min_length=1)
    quantity: int = Field(ge=1)

@app.post('/items/')
def create_item(item: Item):
    return {'name': item.name.strip(), 'quantity': item.quantity}

client = TestClient(app)
good = client.post('/items/', json={'name':'Pencils','quantity':3})
bad = client.post('/items/', json={'name':'Pencils'})
print(good.status_code, good.json())
print(bad.status_code, bad.json()['detail'][0]['loc'])
EXPECTED OUTPUT
200 {'name': 'Pencils', 'quantity': 3}
422 ['body', 'quantity']

A closer look

Follow the reasoning, inspect each result, then try the suggested changes in the console below.

01 / 03

Define the incoming JSON shape

A client sends a JSON document in the request body. In a local FastAPI app, a Pydantic BaseModel makes the expected fields visible as Python annotations and an OpenAPI schema. Add constraints such as minimum quantity only after deciding what the API promises. A missing required field should cause a validation response rather than reaching a route that immediately raises KeyError.

The browser example here uses a Python dictionary to represent a decoded JSON object. It does not start FastAPI; use the local-only code block above to run the actual server. Focus first on the contract: `name` must contain visible text and `quantity` must be a positive integer for this example. The browser task makes those conditions explicit.

PYTHON
body = {'name': 'Pencils'}
print('name' in body)
print('quantity' in body)
EXPECTED OUTPUT
True
False
Follow the reasoning
  1. List the required keys and their value rules.
  2. Distinguish an absent key from a present invalid value.
02 / 03

Separate body from URL parameters

In a FastAPI route such as PUT /items/{item_id}, `item_id` comes from the path, while a parameter annotated as a Pydantic model comes from the JSON body. A simple string parameter that is not in the path is commonly a query parameter. This distinction helps explain why putting JSON fields in the wrong place does not satisfy the request body model.

For the simpler POST /items/ tutorial, there is no path identifier, only the Item body. Test it in the automatically generated /docs UI or with an HTTP client. Sending an object with both required fields should reach the function. Missing quantity should yield 422 with a field location in the response details. Do not claim that a browser-only Python function is an HTTP server.

PYTHON
path = '/items/7'
body = {'name':'Pencils','quantity':3}
print(path.split('/')[-1], sorted(body))
EXPECTED OUTPUT
7 ['name', 'quantity']
Follow the reasoning
  1. Identify the URL path parameters, if any.
  2. Send the Item fields as a JSON request body.
03 / 03

Reject invalid values clearly

A model can enforce syntax and constraints before business logic runs, but the exact coercion policy deserves care. In plain Python, bool is a subclass of int, so `isinstance(True, int)` is true. If an API must accept only literal integer quantities, configure strict validation. The browser task uses exact type checks to make this policy easy to observe.

After normalization, return only the fields the route needs. Strip surrounding whitespace from the name and reject an all-whitespace name. In a production API, tests should submit both a valid JSON body and several invalid bodies through the HTTP layer, then assert status codes and field locations. The local app block is the starting point; the browser exercise checks the decision logic independently.

PYTHON
for quantity in (3, 0, True):
    print(quantity, type(quantity) is int and quantity >= 1)
EXPECTED OUTPUT
3 True
0 False
True False
Follow the reasoning
  1. Reject zero, bool, and missing quantity explicitly.
  2. Return a narrow cleaned dictionary for accepted input.

Try it in Python

Edit the example and run it. Python starts in your browser the first time you click Run.

Python console

Ready to run
Need input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.
Your turnMake normalize_item(body) accept only a dict with a nonempty string name and an integer quantity of at least 1. Strip the name; reject bool and invalid values with ValueError. The prepared call should print {'name': 'Pencils', 'quantity': 3}.
Need a hint?

Check isinstance(body, dict), isinstance(name, str), name.strip(), and type(quantity) is int before returning a clean dictionary.

Complete the task and select Check task to verify your code.

    Quick quiz

    Three questions. You can change your answers and try again.

    01.Which FastAPI parameter is read from a JSON body?

    02.What happens when a required request body field is missing?

    03.Can the browser practice console host the local FastAPI server?

    Typical mistakes

    Everyone meets these errors. See what causes them and how to fix them.

    Using an untyped dict as the entire contract

    COMMON MISTAKE
    def create_item(item: dict): ...
    FIX
    Declare a BaseModel with required typed fields and constraints.

    What happens: The API does not document the expected fields and limits as clearly.
    FastAPI can then validate the body and publish its schema in OpenAPI docs.

    Assuming every integer-like value is acceptable

    COMMON MISTAKE
    isinstance(True, int)
    FIX
    State the chosen coercion policy; use strict constraints when needed.

    What happens: A boolean can be treated as quantity one in handwritten checks.
    Type coercion and business rules deserve an explicit decision.

    Browse this lesson’s problem fixes

    Sources and further reading