API Response Validation
Validating external API responses with models, handling missing fields and unexpected formats.
When I call an external API, the response might not match what the docs promise. How do I validate API responses with Pydantic?
Treat every external API response as untrusted input. Define a model for the expected response shape and validate through it:
from pydantic import BaseModel, Field
from typing import Optional
class WeatherResponse(BaseModel):
city: str
temperature: float
humidity: float
description: str = ""
# Simulate an API response
api_data = {
"city": "Portland",
"temperature": 18.5,
"humidity": 72.0,
"description": "Partly cloudy"
}
weather = WeatherResponse.model_validate(api_data)
print(weather.temperature) # 18.5
If the API returns unexpected data — a missing field, a wrong type, an extra field — Pydantic catches it immediately instead of letting it propagate through your code.
But APIs are messy. Sometimes fields are present, sometimes they're not. How do I handle optional fields gracefully?
Use Optional with defaults for fields that might be missing. Use model_config with extra="ignore" to silently drop unknown fields:
from pydantic import ConfigDict
class ApiUser(BaseModel):
model_config = ConfigDict(extra="ignore")
id: int
name: str
email: Optional[str] = None
avatar_url: Optional[str] = None
bio: str = ""
# API returns extra fields we don't care about
response = {
"id": 42,
"name": "Alice",
"email": "alice@test.com",
"created_at": "2026-01-01", # extra — ignored
"internal_id": "abc123" # extra — ignored
}
user = ApiUser.model_validate(response)
print(user.name) # "Alice"
print(user.avatar_url) # None — not in response, uses default
What about nested API responses? Like a response that has a data wrapper and pagination info?
Model the full response structure with nested models:
class PaginationInfo(BaseModel):
page: int
total_pages: int
total_items: int
class UserItem(BaseModel):
id: int
name: str
email: str
class UserListResponse(BaseModel):
data: list[UserItem]
pagination: PaginationInfo
success: bool = True
api_response = {
"data": [
{"id": 1, "name": "Alice", "email": "a@test.com"},
{"id": 2, "name": "Bob", "email": "b@test.com"}
],
"pagination": {"page": 1, "total_pages": 5, "total_items": 50},
"success": True
}
result = UserListResponse.model_validate(api_response)
print(len(result.data)) # 2
print(result.pagination.total_items) # 50
What if I want to validate but fall back gracefully when the API returns garbage? Like returning a default instead of crashing?
Wrap validation in a try/except and return a fallback:
def safe_parse_response(data: dict, model_class, default=None):
try:
return model_class.model_validate(data)
except Exception:
return default
result = safe_parse_response(bad_data, WeatherResponse, default=None)
if result is None:
print("API returned invalid data — using cached values")
This is defensive programming. You define what you expect, validate it, and handle failures gracefully. Never trust external data — validate everything at the boundary.
Practice your skills
Sign up to write and run code in this lesson.
API Response Validation
Validating external API responses with models, handling missing fields and unexpected formats.
When I call an external API, the response might not match what the docs promise. How do I validate API responses with Pydantic?
Treat every external API response as untrusted input. Define a model for the expected response shape and validate through it:
from pydantic import BaseModel, Field
from typing import Optional
class WeatherResponse(BaseModel):
city: str
temperature: float
humidity: float
description: str = ""
# Simulate an API response
api_data = {
"city": "Portland",
"temperature": 18.5,
"humidity": 72.0,
"description": "Partly cloudy"
}
weather = WeatherResponse.model_validate(api_data)
print(weather.temperature) # 18.5
If the API returns unexpected data — a missing field, a wrong type, an extra field — Pydantic catches it immediately instead of letting it propagate through your code.
But APIs are messy. Sometimes fields are present, sometimes they're not. How do I handle optional fields gracefully?
Use Optional with defaults for fields that might be missing. Use model_config with extra="ignore" to silently drop unknown fields:
from pydantic import ConfigDict
class ApiUser(BaseModel):
model_config = ConfigDict(extra="ignore")
id: int
name: str
email: Optional[str] = None
avatar_url: Optional[str] = None
bio: str = ""
# API returns extra fields we don't care about
response = {
"id": 42,
"name": "Alice",
"email": "alice@test.com",
"created_at": "2026-01-01", # extra — ignored
"internal_id": "abc123" # extra — ignored
}
user = ApiUser.model_validate(response)
print(user.name) # "Alice"
print(user.avatar_url) # None — not in response, uses default
What about nested API responses? Like a response that has a data wrapper and pagination info?
Model the full response structure with nested models:
class PaginationInfo(BaseModel):
page: int
total_pages: int
total_items: int
class UserItem(BaseModel):
id: int
name: str
email: str
class UserListResponse(BaseModel):
data: list[UserItem]
pagination: PaginationInfo
success: bool = True
api_response = {
"data": [
{"id": 1, "name": "Alice", "email": "a@test.com"},
{"id": 2, "name": "Bob", "email": "b@test.com"}
],
"pagination": {"page": 1, "total_pages": 5, "total_items": 50},
"success": True
}
result = UserListResponse.model_validate(api_response)
print(len(result.data)) # 2
print(result.pagination.total_items) # 50
What if I want to validate but fall back gracefully when the API returns garbage? Like returning a default instead of crashing?
Wrap validation in a try/except and return a fallback:
def safe_parse_response(data: dict, model_class, default=None):
try:
return model_class.model_validate(data)
except Exception:
return default
result = safe_parse_response(bad_data, WeatherResponse, default=None)
if result is None:
print("API returned invalid data — using cached values")
This is defensive programming. You define what you expect, validate it, and handle failures gracefully. Never trust external data — validate everything at the boundary.
Practice your skills
Sign up to write and run code in this lesson.