Did you know FastAPI can seamlessly
handle XML responses? While JSON is the go-to for modern APIs, XML is still
crucial for enterprise and legacy systems. FastAPI allows you to create and
document XML APIs efficiently using tools like Response
,
xml.etree.ElementTree
, and external libraries like fastapi-xml
.
Here’s what you’ll learn:
- Basic XML Responses: Use FastAPI's
Response
class to return static XML. - Dynamic XML Creation: Generate XML programmatically with Python's
ElementTree
. - Custom XML Response Classes: Reuse XML formatting logic across endpoints.
- OpenAPI Documentation: Auto-generate XML response schemas and examples for clear API docs.
- Content Negotiation: Serve XML or JSON based on client preferences.
- Error Handling: Format validation and error messages in XML.
- Security Tips: Use libraries like
defusedxml
to prevent XML vulnerabilities.
FastAPI makes XML easy to implement while keeping your APIs secure, flexible, and well-documented. Whether you're working with simple or complex XML structures, this guide covers everything you need.
How to Set Up XML Responses in FastAPI#
FastAPI defaults to JSON for responses, but you can easily configure it to handle XML by using custom objects and XML libraries. In case you are not already familiar with building REST APIs using FastAPI, check out our FastAPI tutorial, written by the FastAPI Expert.
Returning Basic XML with the Response Class#
The simplest way to send XML from a FastAPI endpoint is by using the built-in
Response
class. You just need to specify the response_class
parameter in
your route decorator and set the content type to "application/xml"
. Here's an
example:
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/basic-xml", response_class=Response)
async def get_basic_xml():
xml_content = """<?xml version="1.0" encoding="UTF-8"?>
<user>
<id>123</id>
<name>John Doe</name>
<email>john@example.com</email>
</user>"""
return Response(content=xml_content, media_type="application/xml")
In this setup, the content
parameter is where you pass the XML string, and the
media_type
ensures the response is recognized as XML. This method is perfect
for static XML responses with a fixed structure.
For more dynamic XML needs, you can generate the content programmatically using
libraries like ElementTree
.
Generating Dynamic XML with ElementTree#
When your XML structure depends on changing data, Python's
xml.etree.ElementTree
module is a great tool. It allows you to create XML
content dynamically. Here's an example:
from fastapi import FastAPI, Response
import xml.etree.ElementTree as ET
app = FastAPI()
@app.get("/dynamic-xml", response_class=Response)
async def get_dynamic_xml():
# Create the root element
root = ET.Element("products")
# Example data (could come from a database)
products = [
{"id": 1, "name": "Laptop", "price": 999.99},
{"id": 2, "name": "Mouse", "price": 29.99},
{"id": 3, "name": "Keyboard", "price": 79.99}
]
# Build the XML structure
for product in products:
product_elem = ET.SubElement(root, "product")
id_elem = ET.SubElement(product_elem, "id")
id_elem.text = str(product["id"])
name_elem = ET.SubElement(product_elem, "name")
name_elem.text = product["name"]
price_elem = ET.SubElement(product_elem, "price")
price_elem.text = str(product["price"])
# Convert the XML tree to a string
xml_string = ET.tostring(root, encoding="unicode")
return Response(content=xml_string, media_type="application/xml")
This approach gives you the flexibility to create XML structures that adapt to your data, making it ideal for scenarios where the content varies or is nested.
Using a Custom XML Response Class#
To streamline your XML responses and keep your code organized, you can create a
custom response class by subclassing FastAPI's Response
. This allows you to
encapsulate the XML generation logic and reuse it across multiple endpoints.
Here's how:
from typing import Any
from fastapi import FastAPI, Response
import xml.etree.ElementTree as ET
app = FastAPI()
class CustomXMLResponse(Response):
media_type = "application/xml"
def render(self, content: Any) -> bytes:
root = ET.Element("data")
# Convert dictionary data to XML
if isinstance(content, dict):
for key, value in content.items():
element = ET.SubElement(root, key)
if isinstance(value, (list, tuple)):
for item in value:
item_elem = ET.SubElement(element, "item")
item_elem.text = str(item)
else:
element.text = str(value)
xml_string = ET.tostring(root, encoding="utf8").decode("utf8")
return xml_string.encode("utf8")
@app.get("/custom-xml", response_class=CustomXMLResponse)
async def get_custom_xml():
return {
"message": "Hello World",
"status": "success",
"items": ["item1", "item2", "item3"]
}
This method is especially useful when working with more intricate data formats or when you need consistent XML formatting across multiple endpoints. It simplifies your code by centralizing the XML generation logic into a reusable class.
How to Document XML Responses with OpenAPI#
Clear and detailed documentation is essential for XML APIs, and FastAPI makes this process easier by auto-generating an OpenAPI schema from your code. When you create XML endpoints, this functionality allows developers to quickly grasp the structure and format of responses through interactive documentation.
By combining Pydantic models with FastAPI's OpenAPI features, you can ensure that XML responses are both well-documented and accurately validated. This setup helps developers understand your API's behavior and response structure at a glance.
Setting Up XML Schemas in OpenAPI#
To document XML responses properly, start by defining Pydantic models that reflect your data structure. Even if your endpoint returns XML, these models help FastAPI understand the data format and generate accurate documentation.
Here's an example:
from fastapi import FastAPI, Response
from pydantic import BaseModel
from typing import List
import xml.etree.ElementTree as ET
app = FastAPI()
class User(BaseModel):
id: int
name: str
email: str
class UserList(BaseModel):
users: List[User]
class XMLResponse(Response):
media_type = "application/xml"
@app.get("/users",
response_model=UserList,
response_class=XMLResponse,
responses={
200: {
"description": "A list of users in XML format",
"content": {
"application/xml": {
"example": """<?xml version="1.0" encoding="UTF-8"?>
<users>
<user>
<id>1</id>
<name>John Doe</name>
<email>john@example.com</email>
</user>
</users>"""
}
}
}
})
async def get_users():
users_data = [
{"id": 1, "name": "John Doe", "email": "john@example.com"},
{"id": 2, "name": "Jane Smith", "email": "jane@example.com"}
]
root = ET.Element("users")
for user in users_data:
user_elem = ET.SubElement(root, "user")
for key, value in user.items():
elem = ET.SubElement(user_elem, key)
elem.text = str(value)
xml_string = ET.tostring(root, encoding="unicode")
return XMLResponse(content=xml_string)
In this example, the response_model
parameter defines the expected data
structure, while the response_class
ensures the output is in XML format.
For more complex structures, you can use nested Pydantic models:
class Address(BaseModel):
street: str
city: str
zip_code: str
class UserWithAddress(BaseModel):
id: int
name: str
email: str
address: Address
class UserListWithAddresses(BaseModel):
users: List[UserWithAddress]
Once you've defined the schemas, you can add XML-specific metadata to further enhance your documentation.
Adding XML Metadata to API Endpoints#
While schemas define the structure of your responses, metadata provides
additional context about what the endpoint returns. FastAPI allows you to
include detailed XML-specific metadata using the responses
parameter in path
operation decorators. This metadata shapes how the endpoint is presented in
OpenAPI tools like Swagger UI or
ReDoc.
Here's an example of an endpoint that returns XML-formatted product details:
@app.get("/products/{product_id}",
response_class=XMLResponse,
responses={
200: {
"description": "Product details in XML format",
"content": {
"application/xml": {
"example": """<?xml version="1.0" encoding="UTF-8"?>
<product>
<id>123</id>
<name>Wireless Headphones</name>
<price>149.99</price>
<category>Electronics</category>
</product>""",
"schema": {
"type": "string",
"format": "xml"
}
}
}
},
404: {
"description": "Product not found",
"content": {
"application/xml": {
"example": """<?xml version="1.0" encoding="UTF-8"?>
<error>
<code>404</code>
<message>Product not found</message>
</error>"""
}
}
}
},
tags=["Products"],
summary="Get product by ID",
description="Retrieves detailed XML responses about a specific product")
async def get_product(product_id: int):
# Implement your logic here
pass
If your API supports both JSON and XML responses, you can document both formats within a single endpoint. Here's how:
@app.get("/items/{item_id}",
responses={
200: {
"description": "Item details",
"content": {
"application/json": {
"example": {"id": 1, "name": "Sample Item", "price": 29.99}
},
"application/xml": {
"example": """<?xml version="1.0" encoding="UTF-8"?>
<item>
<id>1</id>
<name>Sample Item</name>
<price>29.99</price>
</item>"""
}
}
}
})
async def get_item(item_id: int):
# Implementation with content negotiation
pass
This method ensures your OpenAPI documentation reflects all supported response formats, making it easier for developers to interact with your API's XML endpoints effectively.
Using External Libraries for XML Handling#
Python's built-in xml.etree.ElementTree
module is great for handling basic XML
tasks, but when you're working with FastAPI, specialized libraries can make life
a lot easier. One such library is fastapi-xml, which simplifies XML
processing and integrates seamlessly with FastAPI.
Working with the fastapi-xml Library#
The fastapi-xml library is specifically designed to enhance XML handling in FastAPI. It leverages xsdata for XML serialization and deserialization, combining this with FastAPI's routing and response features to create a smooth development experience.
"Together, fastapi handles xml data structures using dataclasses generated by xsdata. Whilst, fastapi handles the api calls, xsdata covers xml serialisation and deserialization. In addition, openapi support works as well."
- fastapi-xml · PyPI
To start using it, simply install the library via pip:
pip install fastapi-xml
This library introduces key components like XmlRoute
, XmlAppResponse
, and
XmlBody
, which simplify tasks such as routing, formatting responses, and
processing XML data. Here's a quick example:
from fastapi import FastAPI
from fastapi_xml import XmlRoute, XmlAppResponse, XmlBody, add_openapi_extension
from dataclasses import dataclass
@dataclass
class HelloWorld:
message: str
app = FastAPI(
default_response_class=XmlAppResponse,
routes=[XmlRoute]
)
@app.post("/echo")
async def echo_message(body: XmlBody[HelloWorld]) -> HelloWorld:
# Modify the incoming message
body.message += " For ever!"
return body
# Enable OpenAPI support for XML responses
add_openapi_extension(app)
In this example, the HelloWorld
dataclass defines the structure of the XML
data. The XmlBody[HelloWorld]
parameter automatically converts incoming XML
into the dataclass, while the return type ensures the response is serialized
back into XML. This approach eliminates the need to manually construct or parse
XML trees, making the code cleaner and easier to manage.
The library also handles more complex XML structures effortlessly. You can define nested dataclasses, include lists of elements, and even manage attributes. Check out this example:
from dataclasses import dataclass, field
from typing import List
@dataclass
class Product:
id: int
name: str
price: float
category: str = field(metadata={"type": "attribute"})
@dataclass
class ProductCatalog:
products: List[Product]
total_count: int = field(metadata={"type": "attribute"})
@app.get("/catalog")
async def get_catalog() -> ProductCatalog:
products = [
Product(id=1, name="Wireless Mouse", price=29.99, category="Electronics"),
Product(id=2, name="USB Cable", price=12.50, category="Accessories")
]
return ProductCatalog(products=products, total_count=len(products))
Here, the ProductCatalog
dataclass includes a list of Product
objects,
showcasing how the library can handle nested and attribute-rich XML structures.
Another standout feature of fastapi-xml is its automatic OpenAPI
integration. By using the add_openapi_extension(app)
function, you can ensure
that XML endpoints are properly documented in tools like Swagger UI and ReDoc.
Best Practices for Using fastapi-xml#
When incorporating external libraries like fastapi-xml, it's essential to
manage dependencies carefully. Pin specific versions of your dependencies in a
requirements.txt
file to maintain stability in production. For larger
projects, tools like Poetry can help you manage
dependencies more effectively.
While fastapi-xml handles typical API payloads efficiently, processing very large XML files can strain memory resources. For such cases, consider monitoring performance and exploring scalable solutions like Dask to handle heavy workloads.
With minimal setup, fastapi-xml provides a powerful way to manage XML in FastAPI applications, making it a great choice for most XML-related tasks.

Over 10,000 developers trust Zuplo to secure, document, and monetize their APIs
Learn MoreBest Practices for XML APIs in FastAPI#
Building reliable XML APIs goes beyond generating responses; it involves managing client interactions, handling errors effectively, and ensuring security for consistent performance in production environments.
How to Implement Content Negotiation#
Content negotiation enables your API to deliver responses in formats that match
client preferences. FastAPI handles this by examining the Accept
header in
incoming requests. For example, if the client specifies application/xml
in the
header, the API returns an XML response. If the header is absent or set to
application/json
, JSON is used as the default.
Here’s an example:
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse, Response
from xml.etree.ElementTree import Element, tostring
from dataclasses import dataclass
@dataclass
class User:
id: int
name: str
email: str
app = FastAPI()
@app.get("/users/{user_id}")
async def get_user(user_id: int, request: Request):
# Example user data
user = User(id=user_id, name="John Doe", email="john@example.com")
# Check the Accept header
accept_header = request.headers.get("accept", "")
if "application/xml" in accept_header:
# Generate XML response
root = Element("user")
id_elem = Element("id")
id_elem.text = str(user.id)
name_elem = Element("name")
name_elem.text = user.name
email_elem = Element("email")
email_elem.text = user.email
root.extend([id_elem, name_elem, email_elem])
xml_content = tostring(root, encoding='unicode')
return Response(content=xml_content, media_type="application/xml")
elif "application/json" in accept_header or not accept_header:
# Default to JSON response
return {"id": user.id, "name": user.name, "email": user.email}
else:
# Handle unsupported media types
raise HTTPException(status_code=406, detail="Not Acceptable")
For clients unable to customize request headers, you can use query parameters to specify the desired format:
@app.get("/users/{user_id}")
async def get_user_with_format(user_id: int, format: str = "json"):
user = User(id=user_id, name="John Doe", email="john@example.com")
if format.lower() == "xml":
# Generate XML response
root = Element("user")
# Add XML structure here
xml_content = tostring(root, encoding='unicode')
return Response(content=xml_content, media_type="application/xml")
elif format.lower() == "json":
return {"id": user.id, "name": user.name, "email": user.email}
else:
raise HTTPException(status_code=400, detail="Unsupported format")
Both methods ensure flexibility in serving XML and JSON responses while preparing for consistent error handling in XML.
Formatting Error Responses in XML#
To maintain a seamless client experience, error responses should match the requested content type. FastAPI allows you to define custom exception handlers to format errors in XML.
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import Response
from fastapi.exception_handlers import http_exception_handler
from xml.etree.ElementTree import Element, SubElement, tostring
app = FastAPI()
@app.exception_handler(HTTPException)
async def xml_http_exception_handler(request: Request, exc: HTTPException):
accept_header = request.headers.get("accept", "")
if "application/xml" in accept_header:
# Create an XML error response
error_root = Element("error")
code_elem = SubElement(error_root, "code")
code_elem.text = str(exc.status_code)
message_elem = SubElement(error_root, "message")
message_elem.text = exc.detail
docs_elem = SubElement(error_root, "documentation")
docs_elem.text = "https://api.example.com/docs/errors"
timestamp_elem = SubElement(error_root, "timestamp")
timestamp_elem.text = "2025-06-11T10:30:00Z"
xml_content = tostring(error_root, encoding='unicode')
return Response(
content=xml_content,
status_code=exc.status_code,
media_type="application/xml"
)
# Default to JSON error handling
return await http_exception_handler(request, exc)
Validation errors can also follow this structure for consistency. Here’s how you can handle validation errors in XML:
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel, ValidationError
from xml.etree.ElementTree import Element, SubElement, tostring
class CreateUserRequest(BaseModel):
name: str
email: str
age: int
@app.post("/users")
async def create_user(user_data: CreateUserRequest, request: Request):
try:
# Simulate user creation process
return {"message": "User created successfully"}
except ValidationError as e:
accept_header = request.headers.get("accept", "")
if "application/xml" in accept_header:
error_root = Element("validation_error")
code_elem = SubElement(error_root, "code")
code_elem.text = "422"
message_elem = SubElement(error_root, "message")
message_elem.text = "Validation failed"
errors_elem = SubElement(error_root, "errors")
for error in e.errors():
field_error = SubElement(errors_elem, "field_error")
field_elem = SubElement(field_error, "field")
field_elem.text = ".".join(str(loc) for loc in error["loc"])
error_msg = SubElement(field_error, "error")
error_msg.text = error["msg"]
xml_content = tostring(error_root, encoding='unicode')
return Response(
content=xml_content,
status_code=422,
media_type="application/xml"
)
This approach ensures that both general and validation errors are formatted consistently, enhancing client usability.
Security Considerations#
Handling XML securely is a critical aspect of API development. Python’s built-in
xml
library is susceptible to attacks like XML External Entity (XXE) and "XML
bombs", which can expose sensitive data or overload system resources. For secure
parsing, use the defusedxml
library:
import defusedxml.ElementTree as ET
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
@app.post("/process-xml")
async def process_xml_data(request: Request):
try:
xml_data = await request.body()
# Securely parse XML
root = ET.fromstring(xml_data)
# Process XML safely
return {"status": "XML processed successfully"}
except ET.ParseError:
raise HTTPException(status_code=400, detail="Invalid XML format")
except Exception as e:
raise HTTPException(status_code=500, detail="XML processing failed")
Conclusion#
Creating XML responses in FastAPI requires striking a balance between
functionality and ease of maintenance. A key method involves using
xml.etree.ElementTree
to construct XML data, while setting the response's
media type to application/xml
. This allows your API to deliver XML outputs
effectively, all while benefiting from FastAPI's built-in OpenAPI support for
documentation and integration.
Clear documentation of your XML endpoints is crucial for encouraging API
adoption. By utilizing the responses
parameter in route decorators and
tailoring the OpenAPI schema, you can provide detailed information about your
XML endpoints. This includes specifying media types, explaining the data
structure, and offering examples, which makes it easier for developers to work
with your API.
Security is another critical aspect of implementing XML APIs. Validating XML
inputs and using libraries like defusedxml
help protect against common
vulnerabilities. These security measures complement features like content
negotiation and error handling, ensuring your API is both flexible and secure.
Lastly, rigorous testing ensures your XML endpoints perform as expected. By following these practices, you can transform FastAPI's JSON-centric design into a versatile tool for delivering XML responses. This approach meets a variety of enterprise requirements while maintaining the framework's simplicity, powerful documentation features, and overall usability. These strategies will help you build reliable and well-documented XML APIs with FastAPI.
FAQs#
How can I protect my FastAPI XML responses from security risks like XXE attacks?#
To protect your FastAPI XML responses from XML External Entity (XXE) attacks, here are some key precautions you should take:
- Turn off external entity processing in your XML parser. Libraries like
lxml
orxml.etree.ElementTree
often provide options to disable this feature, blocking unsafe external references from being processed. - Validate and sanitize all incoming XML data. Ensure that no malicious content sneaks through by using strict schema validation, such as XML Schema Definition (XSD), to only allow well-formed and expected XML structures.
- Use XML parsing libraries that are specifically built to address XXE vulnerabilities, as these often come with built-in safeguards.
By following these guidelines, you can significantly lower the risk of XXE attacks and ensure your API remains secure while handling XML responses.
What makes the fastapi-xml library a better choice than Python's xml.etree.ElementTree for handling XML in FastAPI applications?#
The fastapi-xml library brings several perks when compared to Python's
built-in xml.etree.ElementTree
, especially for those working with XML in
FastAPI:
- Easier XML Management: With its straightforward and intuitive API, fastapi-xml simplifies the process of creating and managing XML structures. This contrasts with the more hands-on, manual approach required by ElementTree.
- Smooth FastAPI Integration: It integrates seamlessly with FastAPI's automatic OpenAPI documentation, ensuring that XML responses are clearly defined and well-represented in your API's schema.
- Async Support: fastapi-xml is designed to handle asynchronous operations, making it ideal for building high-performing, non-blocking APIs. In comparison, ElementTree doesn't natively support async functionality.
Using fastapi-xml allows developers to efficiently generate XML responses while staying aligned with FastAPI's performance capabilities and core features.
How does FastAPI handle content negotiation to serve XML and JSON responses based on client needs?#
FastAPI offers content negotiation, which allows the server to respond in
various formats, such as JSON or XML, depending on what the client requests.
These preferences are typically specified using the HTTP Accept
header or
through query parameters in the request.
When a request comes in, FastAPI checks the Accept
header to figure out the
preferred format. By default, if JSON is requested - or if no specific
preference is stated - the server provides a JSON response. If XML is needed,
you can set up custom logic to generate and return an XMLResponse
. This
flexibility enables clients to get data in their preferred format without
needing separate endpoints for each type, streamlining API design and improving
usability.