Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 13

FastAPI Error Handling and Validation Responses

Recommended resources

Hello! Let's dive into our next lesson.

In our last session, we saw how response_model gives you control over your API's output, ensuring a consistent and secure data contract. You've now defined schemas for both incoming requests and outgoing responses. A key feature we've touched on is FastAPI's automatic validation. But what happens when that validation fails?

This lesson directly addresses the learning outcome: Handle validation errors and interpret FastAPI's automatic error responses. We will explore the detailed error messages FastAPI provides when input data is invalid and learn how to raise our own custom errors for scenarios beyond simple data validation. Understanding how your API communicates failure is just as important as knowing how it communicates success.

1. Automatic Validation Errors: The 422 Unprocessable Entity

One of FastAPI's most powerful features is its tight integration with Pydantic. When you define a type hint for a path parameter, query parameter, or request body, FastAPI uses it as a validation rule. If an incoming request violates this rule, FastAPI automatically intercepts it and returns a specific HTTP error.

Let's start by reading a concise explanation of this automatic process.

FastAPI Request Body Validation

This article from ApX Machine Learning clearly outlines the steps FastAPI takes to validate a request body, culminating in what happens when validation fails.

Read the numbered list under the heading 'Here's what FastAPI automatically does...'. Pay close attention to step 5, which describes the automatic generation of an HTTP 422 Unprocessable Entity error.

As the article mentions, you get a 422 Unprocessable Entity status code. This code signifies that the server understands the request's content type and the syntax of the request entity is correct, but it was unable to process the contained instructions. In simpler terms: "I know you sent me JSON, but the data inside it is wrong."


2. Interpreting the Error Response

FastAPI doesn't just say "something is wrong"; it gives you a detailed JSON response explaining exactly what is wrong. This is incredibly helpful for debugging and for client-side applications that need to display user-friendly error messages.

The error response has a standard structure. Let's examine it.

FastAPI Request Body Validation

This next section of the same article provides a perfect example of an invalid request and the corresponding 422 error response, breaking down its structure.

Read the section 'Example: Invalid Request'. Focus on the JSON response and its detail field. Note how it's a list, with each item containing three key pieces of information: loc, msg, and type.

The three key fields in each error object are:

  • loc: A list indicating the location of the error in the incoming data. For a request body, it will be ["body", "field_name"]. For a path parameter, it would be ["path", "param_name"].
  • msg: A human-readable message explaining the error.
  • type: A machine-readable string indicating the type of error (e.g., value_error.missing, type_error.float).

Here is a visual example of such an error.

FastAPI Validation Error: Unprocessable Entity (422)
This image shows a typical FastAPI 422 error response. You can clearly see the `detail` list identifying the error's `loc` (location), `msg` (message), and `type`.

Let's see this in action. The following video demonstrates how these errors appear in the interactive documentation when you test an endpoint with invalid data.

Python FastAPI Tutorial (Part 4): Pydantic Schemas - Request and Response Validation

Corey Schafer's tutorial provides an excellent hands-on demonstration of triggering and inspecting these validation errors.

Watch from 17:58 to 19:28. Observe how sending a request with a too-short title or a missing field immediately results in a 422 error, and inspect the detailed response body that FastAPI generates.

This automatic validation isn't limited to request bodies. It works for path and query parameters too, based on their Python type hints.

FastAPI 422 Unprocessable Entity Error in Swagger UI
Here, a validation rule (ID must be greater than 0) was applied to a path parameter. Sending an invalid ID (`0`) correctly triggers a 422 error with a clear message.

3. Raising Your Own Errors with HTTPException

Automatic validation is for data that is structurally or typographically incorrect. But what about errors related to your application's business logic?

  • A client requests a resource with an ID that doesn't exist in your database (/items/999).
  • A user tries to perform an action for which they don't have permission.
  • An ML model receives validly structured input that is nonsensical in the problem context (e.g., a house area of -100 sqft).

In these cases, the data is "valid" according to Pydantic, but "invalid" according to your rules. For this, you need to manually raise an exception. FastAPI provides HTTPException for this purpose.

Handling Errors - FastAPI

The official FastAPI documentation explains how and why to use HTTPException.

Read the sections 'Handling Errors' and 'Use HTTPException'. Note that HTTPException is a Python exception, so you raise it, not return it. This will immediately halt the function's execution and send the error response.

The key is to use the correct HTTP status code to communicate the nature of the error. Common client-side error codes include:

  • 400 Bad Request: A generic error for when the client sends a request that the server cannot process, often due to a business logic failure.
  • 404 Not Found: The requested resource does not exist.
  • 403 Forbidden: The client is authenticated, but not authorized to perform the action.
  • 409 Conflict: The request could not be completed due to a conflict with the current state of the resource (e.g., trying to create a user with an email that already exists).

Let's watch a practical example of refactoring code to use HTTPException for a "not found" scenario.

Python FastAPI Tutorial (Part 3): Path Parameters - Validation and Error Handling

In this clip, Corey Schafer demonstrates exactly why returning a simple dictionary is bad practice and how to fix it by raising a proper HTTPException with a 404 status code.

Watch from 04:40 to 09:40. The first part (until 06:52) shows the problem: returning an error message with a 200 OK status. The second part shows the solution using raise HTTPException and how it results in the correct 404 Not Found status code.


4. Hands-On Practice: Adding Business Logic Validation

Let's apply this to our ML prediction endpoint. While Pydantic ensures we get numbers for area_sqft and num_bedrooms, it doesn't know that a value of 0 for num_bedrooms might be illogical for a house price prediction. We'll add a check for this using HTTPException.

Task:

In your main.py file:

  1. Make sure you have HTTPException imported from FastAPI: from fastapi import FastAPI, HTTPException.
  2. In your predict_house_price endpoint, add a business logic check right after the function definition. If house_data.num_bedrooms is less than 1, it's an invalid request.
  3. If the condition is true, raise an HTTPException with a status_code of 400 (Bad Request) and a helpful detail message, like "Number of bedrooms must be a positive number."
  4. Run your app and test the endpoint in the Swagger UI:
    • First, send a request with num_bedrooms: 0. You should see your custom 400 Bad Request error.
    • Then, send a request with area_sqft: "invalid-string". You should see FastAPI's automatic 422 Unprocessable Entity error. This will help solidify the difference between the two types of errors.
Click here for the solution



# In main.py
# You should already have your Pydantic models and the app instance defined.
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

app = FastAPI()

class HousePredictionInput(BaseModel):
  area_sqft: float = Field(..., gt=0, description="Area of the house in square feet")
  num_bedrooms: int = Field(..., gt=0, description="Number of bedrooms")
  has_garage: bool

class HousePredictionOutput(BaseModel):
  predicted_price: float

@app.post("/predict/house-price", response_model=HousePredictionOutput)
async def predict_house_price(house_data: HousePredictionInput):



  # Our new business logic check
  if house_data.num_bedrooms < 1:
      raise HTTPException(status_code=400, detail="Number of bedrooms must be a positive number.")




  # Dummy prediction logic (same as before)
  predicted_price = 150_000 + (house_data.area_sqft * 150) + (house_data.num_bedrooms * 20_000)
  if house_data.has_garage:
      predicted_price += 25_000
  
  return {"predicted_price": predicted_price}

Note: In the solution, the Pydantic model for HousePredictionInput also includes gt=0 validation. The hands-on task asks you to use HTTPException for a similar check (< 1) to practice the concept. Both achieve a similar outcome, but HTTPException is more flexible for complex business rules that can't be expressed in Pydantic.


Conclusion

Excellent work! You now have a solid understanding of FastAPI's two-tiered error handling system.

Key Takeaways:

  • Automatic Validation Errors: FastAPI automatically returns a 422 Unprocessable Entity error when incoming data fails Pydantic validation. The JSON response detail field tells you the precise loc (location), msg (message), and type of each error.
  • Business Logic Errors: For custom validation rules (e.g., checking if a resource exists, validating logical constraints), you should manually raise HTTPException.
  • Status Codes Matter: Using the correct HTTP status code (422, 404, 400, etc.) is essential for building a predictable and reliable API that clients can easily integrate with.

In our next lesson, we will continue building on our knowledge of Pydantic by learning how to combine path, query, and body parameters in a single endpoint, seeing how FastAPI seamlessly handles validation for all of them at once.