FastAPI 422 Unprocessable Entity on Optional Query Parameters
Set default value default=None in query parameter definitions (limit: int | None = None or Query(default=None)) so missing parameters do not fail validation.
Root Cause Analysis
This error occurs when a client sends an HTTP request to a FastAPI endpoint omitting a query parameter, but FastAPI returns HTTP 422 Unprocessable Entity because the parameter was defined with type int (or str) without an explicit = None default value.
1. Type Annotation Without Default Value Equals Required in FastAPI
In FastAPI and Pydantic, declaring def get_items(limit: int): marks limit as a mandatory query parameter. Even if annotated as limit: Optional[int], omitting the default = None still marks the parameter as required by OpenAPI specifications.
2. Pydantic 2.0 Strict Validation Rules
In Pydantic 2.0+, limit: Optional[int] without = None means 'value may be None or int, but the field MUST be provided in the payload'. To make the field truly optional when omitted from HTTP requests, an explicit default = None is mandatory.
3. Query Parameter Coercion Failures
Clients passing empty strings (e.g. ?limit=) for numeric parameters cause Pydantic coercion errors.
4. Swagger UI Documentation Inconsistencies
Omitting = None marks the query parameter with a red asterisk (*) in Swagger UI, confusing API consumers.
Reproduction Code (MCVE)
from pydantic import BaseModel
class QueryModel(BaseModel):
limit: int # Missing default=None makes parameter required
# Instantiating model without mandatory query parameter raises ValidationError
QueryModel()
Solution 1: Assign = None Default to Optional Query Parameters
Use int | None = None (Python 3.10+) or Optional[int] = None to mark parameters as truly optional.
from typing import Optional
# Proper FastAPI route definition for optional query parameters
def get_items(search: str | None = None, limit: int | None = 10, offset: int = 0):
params = {'search': search, 'limit': limit, 'offset': offset}
print(f'Resolved query parameters: {params}')
return params
print(get_items()) # Can be called without any arguments
Solution 2: Use FastAPI Query() Parameter Helper
Use Query(default=None) for advanced validation metadata (min_length, max_length, regex).
class MockQuery:
def __init__(self, default=None, **kwargs):
self.default = default
def search_endpoint(q: str = MockQuery(default=None).default):
return {'query': q}
print(search_endpoint())
A common mistake is writing q: Optional[str] without = None. Always write q: Optional[str] = None or q: str | None = None. Edge cases occur with Boolean query parameters: clients sending ?active=false or ?active=0 are coerced to False, but sending ?active= (empty string) will fail validation unless active: bool | None = None is defined. Contrast this error with 400 Bad Request, which is returned for custom business logic rejections rather than Pydantic schema validation failures.