Skip to content

08 Status Codes

DodaTech 4 min read

title: Status Codes in FastAPI REST APIs weight: 18 date: 2026-06-28 lastmod: 2026-06-28 description: Learn HTTP status codes in FastAPI including setting status codes, custom error responses, status module constants, and handlers for common HTTP exceptions. tags: [api-development, fastapi]


FastAPI handles HTTP status codes using the status_code decorator parameter, the status module constants, HTTPException for error responses, and custom exception handlers for consistent status code usage.

```mermaid
flowchart TD
  A[Status Codes] --> B[Decorator]
  A --> C[HTTPException]
  A --> D[Exception Handlers]
  B --> E[status_code=201]
  B --> F[status_code=204]
  C --> G[raise HTTPException(404)]
  D --> H[Custom 422 handler]
  style A fill:#e1f5fe
  style B fill:#c8e6c9
  style C fill:#fff9c4

Set status codes in the decorator (status_code=201 for creation), in HTTPException (status_code=404 for not found), or by returning a Response object directly. FastAPI's status module provides named constants like HTTP_201_CREATED for readability.

Think of status codes like the result of a vending machine transaction. 200 OK means your snack is dispensed. 201 Created means your custom order is ready. 404 Not Found means the snack is sold out. 422 Unprocessable means you entered an invalid selection code.

Example: Setting Status Codes

from fastapi import FastAPI, status, HTTPException
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float

items_db = {}

@app.post("/api/items", status_code=status.HTTP_201_CREATED)
def create_item(item: Item):
    item_id = len(items_db) + 1
    items_db[item_id] = item.model_dump()
    return {"id": item_id, **items_db[item_id]}

@app.get("/api/items/{item_id}")
def get_item(item_id: int):
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    return items_db[item_id]

@app.delete("/api/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_item(item_id: int):
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    del items_db[item_id]

Example: HTTPException with Status Codes

from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse

app = FastAPI()

@app.get("/api/orders/{order_id}")
def get_order(order_id: int):
    # 400 - Bad request
    if order_id <= 0:
        raise HTTPException(
            status_code=400,
            detail="Order ID must be positive",
            headers={"X-Error-Code": "INVALID_ID"}
        )

    # 401 - Unauthorized (missing auth)
    # (Would be caught by auth middleware)

    # 403 - Forbidden (no permission)
    # raise HTTPException(status_code=403, detail="Insufficient permissions")

    # 404 - Not found
    order = find_order(order_id)
    if not order:
        raise HTTPException(
            status_code=404,
            detail=f"Order {order_id} not found"
        )

    # 409 - Conflict
    if order["status"] == "cancelled":
        raise HTTPException(
            status_code=409,
            detail="Cannot modify a cancelled order"
        )

    # 422 - Validation error (automatic with Pydantic)
    # FastAPI returns 422 automatically when request body fails validation

    return order

Example: Custom Exception Handler

from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse

app = FastAPI()

class NotFoundException(HTTPException):
    def __init__(self, resource: str, resource_id):
        super().__init__(
            status_code=404,
            detail={
                "code": "NOT_FOUND",
                "message": f"{resource} with id {resource_id} not found",
                "resource": resource,
                "resource_id": resource_id
            }
        )

class ValidationException(HTTPException):
    def __init__(self, field: str, message: str):
        super().__init__(
            status_code=422,
            detail={
                "code": "VALIDATION_ERROR",
                "field": field,
                "message": message
            }
        )

# Custom handler for specific exceptions
@app.exception_handler(NotFoundException)
async def not_found_handler(request: Request, exc: NotFoundException):
    return JSONResponse(
        status_code=exc.status_code,
        content={
            "status": "error",
            "error": exc.detail
        }
    )

@app.get("/api/users/{user_id}")
def get_user(user_id: int):
    raise NotFoundException("User", user_id)

Common Mistakes

  1. Using 200 for creation — Successful resource creation should return 201, not 200. The 201 status tells clients a resource was created.
  2. Using 500 for client errors — If the client sends bad data, return 400 or 422. 500 means the server failed, not the client.
  3. Not distinguishing 401 and 403 — 401 means unauthenticated (not logged in). 403 means authenticated but not authorized. Use the correct code.
  4. Using 200 for empty responses — If there is no content to return, use 204 No Content, not 200 with an empty body.
  5. Not using status module constants — Magic numbers like 404 are less readable than status.HTTP_404_NOT_FOUND. Use the constants.

Practice Questions

  1. What status code should a successful DELETE return?
  2. What is the difference between 401 and 403?
  3. How do you pass custom headers with HTTPException?
  4. What status code does FastAPI return for validation errors automatically?
  5. Challenge: Create an API with custom exception classes for each error type (NotFound, Conflict, Forbidden, Unauthorized). Implement a global exception handler that returns consistent JSON error responses with status codes.

FAQ

What is the difference between HTTPException and custom exception handlers?

HTTPException is for inline errors in route handlers. Custom exception handlers allow centralized error formatting for specific exception types.

Can I return a custom status code without raising an exception?

Yes, use a Response object: return JSONResponse(content={...}, status_code=status.HTTP_201_CREATED).

What happens if I set status_code in both decorator and return?

The decorator status_code is the default. If you return a Response with a different status code, the returned status code takes precedence.

How do I add custom headers to error responses?

Use the headers parameter in HTTPException: HTTPException(status_code=429, headers={'Retry-After': '120'}).

What is the default status code for FastAPI routes?

GET, PUT, PATCH, and DELETE default to 200. POST defaults to 200 if no status_code is specified. Always explicitly set status_code.

Mini Project

Build a library management API with proper status code handling for all endpoints. Include: 201 for resource creation, 204 for deletion, 400 for bad requests, 404 for not found, 409 for conflicts (duplicate ISBN), 422 for validation errors, and a custom exception handler that formats all errors consistently.

What's Next

Now learn about dependency injection in Building REST APIs with FastAPI.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro