How to Represent XML Responses in FastAPI Docs
In the ever-evolving landscape of modern web services, JSON has undeniably ascended as the de facto standard for data exchange, lauded for its simplicity, human readability, and seamless integration with JavaScript. Yet, beneath the surface of this JSON-centric world, a powerful and venerable format, XML, continues to thrive in specific, often critical, domains. Enterprises with deep-rooted legacy systems, industry-specific protocols, and complex data structures frequently rely on XML for its robust schema validation, explicit structure, and extensive tooling. As developers embracing modern frameworks like FastAPI, we are empowered by its speed, conciseness, and automatic interactive documentation, powered by the OpenAPI Specification. However, this inherent JSON-first approach often presents a unique challenge: how do we accurately and effectively represent XML responses within FastAPI's automatically generated documentation, ensuring clarity and utility for all API consumers?
This article delves into the intricacies of integrating XML response documentation within FastAPI, guiding you through practical strategies to bridge the gap between XML's structured data and FastAPI's OpenAPI-driven documentation. We will explore why XML remains relevant, dissect FastAPI's documentation mechanisms, and provide step-by-step instructions, complete with code examples, to ensure your API's documentation is comprehensive, regardless of the data format. By mastering these techniques, you'll not only enhance the discoverability and usability of your APIs for a wider audience but also elevate your overall API Developer Portal experience, making your services truly accessible and understandable to every consuming client, whether they speak JSON or XML.
Understanding FastAPI's Documentation Mechanism: The OpenAPI Foundation
FastAPI, built on Starlette and Pydantic, offers a truly remarkable developer experience, not least because of its automatic interactive API documentation. This documentation, visible through Swagger UI and ReDoc, isn't magic; it's meticulously crafted from the OpenAPI Specification (OAS), formerly known as Swagger Specification. To effectively document XML responses, we must first understand this underlying machinery.
The OpenAPI Specification is a language-agnostic, human-readable description format for RESTful APIs. It allows developers to describe the entire surface area of an API, including endpoints, operations (GET, POST, PUT, DELETE), parameters, authentication methods, and, crucially for our discussion, request and response bodies. When you define a FastAPI path operation, you're implicitly providing information that FastAPI then translates into an OpenAPI schema. Pydantic models play a pivotal role here: when you define a Pydantic model as a request body or a response model, FastAPI automatically infers the JSON Schema representation of that model and embeds it directly into the generated OpenAPI document. This is why, out-of-the-box, FastAPI's documentation shines when dealing with JSON.
For instance, consider a simple Pydantic model:
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
email: str
If you use this User model as a response_model in a FastAPI endpoint, the OpenAPI documentation will automatically generate a JSON Schema component for User like this:
"components": {
"schemas": {
"User": {
"title": "User",
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "integer"
},
"name": {
"title": "Name",
"type": "string"
},
"email": {
"title": "Email",
"type": "string"
}
},
"required": ["id", "name", "email"]
}
}
}
And then, for the specific endpoint, it would reference this schema, indicating that the response for application/json will conform to the User schema. This automation is incredibly powerful for APIs that predominantly communicate using JSON.
The challenge arises because the OpenAPI Specification, while flexible, primarily uses JSON Schema to describe data structures. While you can indicate different media_types (like application/xml) for request and response bodies, the schema associated with these media types is still, by default, a JSON Schema. This means that if you simply state content={"application/xml": {"schema": {"type": "string"}}} in your OpenAPI definition, the documentation will correctly show that the endpoint returns XML, but it will only describe it as a generic string, offering no insight into the actual XML structure, element names, attributes, or namespaces. This generic description is often insufficient for developers consuming your api, especially those integrating with existing XML-dependent systems.
To overcome this, we need to move beyond FastAPI's default schema inference for JSON and leverage other powerful features of the OpenAPI Specification, particularly the examples field within the content object. This allows us to provide concrete, illustrative examples of the XML response structure, guiding API consumers on exactly what to expect. By understanding this distinction β that OpenAPI's schema is primarily JSON Schema, and for non-JSON formats like XML, examples become the primary tool for detailed structural documentation β we unlock the ability to document complex XML responses effectively within FastAPI's interactive documentation, enhancing the value of your API Developer Portal.
The Persistence of XML: Where and Why It Still Matters
While JSON might be the lingua franca of modern web services, to dismiss XML entirely would be a grave oversight. Its roots run deep, intertwining with the very fabric of enterprise systems and specialized industries that predate the widespread adoption of RESTful APIs and JSON. Understanding XML's enduring relevance is crucial for any developer aiming to build comprehensive and interoperable APIs.
XML, or eXtensible Markup Language, emerged in the late 1990s as a successor to SGML, designed for internet document exchange. It quickly became the backbone of early web services through SOAP (Simple Object Access Protocol), defining a robust, self-descriptive, and machine-readable way to structure data. Its key strength lies in its ability to define custom tags and attributes, allowing for highly structured and semantically rich data representations. Furthermore, technologies like XML Schema Definition (XSD) provide a powerful mechanism for validating the structure and content of XML documents, ensuring data integrity and consistency, a feature that JSON only recently started to gain with JSON Schema, which is less universally adopted for rigid data contracts than XSD.
Today, XML continues to be critical in several key areas:
- Legacy Enterprise Systems: Many large organizations, particularly in sectors like finance, government, and healthcare, have multi-decade-old backend systems that are deeply integrated using XML. These systems, often mission-critical and incredibly complex, cannot be re-written overnight. When modernizing their
apilandscape, these organizations need to expose data from these systems, and often, the most straightforward approach is to continue using XML as the data exchange format, especially for internal APIs or B2B integrations. For instance, a financial institution might use XML for internal message queues or data archival, and any newapiinteracting with these components will likely need to understand or produce XML. - Industry Standards and Protocols: Specific industries have established standards that mandate the use of XML.
- Healthcare: HL7 (Health Level Seven) is a widely used set of international standards for transfer of clinical and administrative data between software applications used by various healthcare providers. While FHIR (Fast Healthcare Interoperability Resources) is gaining traction with JSON, older HL7 versions predominantly use XML.
- Finance: FIX (Financial Information eXchange) Protocol, used for electronic communication of trade-related messages, often leverages XML for more complex or extended message types, although traditional FIX is tag-value based. XBRL (eXtensible Business Reporting Language) is another XML-based standard used for financial reporting and auditing.
- Publishing and Content Management: JATS (Journal Article Tag Suite) and NLM DTDs are XML-based standards for tagging and exchanging scientific and medical journal articles.
- Supply Chain and Logistics: Certain Electronic Data Interchange (EDI) standards, while traditionally proprietary, have modern XML-based counterparts (like ebXML) for B2B communication.
- Manufacturing and IoT: Some industrial control systems or specialized sensor networks might still output or consume data in XML format due to existing standards or hardware limitations.
- Complex Data Structures and Metadata: XML excels at representing highly hierarchical data, especially when intricate metadata needs to be embedded directly within the data structure. Its explicit closing tags and attribute capabilities allow for richer contextual information compared to JSON, where metadata is often a separate property. This can be particularly useful in scientific data, document markup, or configuration files where precise semantics and hierarchical relationships are paramount.
- Schema Validation and Digital Signatures: As mentioned, XSD provides a robust mechanism for strictly validating the structure and content of XML documents against a predefined schema. This is critical in scenarios where data integrity and adherence to strict contracts are non-negotiable (e.g., regulatory reporting). Furthermore, XML has built-in support for digital signatures (XML-DSig) and encryption (XML-Enc), making it suitable for secure, verifiable data exchange where non-repudiation and confidentiality are paramount. While JSON also has security mechanisms, XML's are more mature and integrated into its ecosystem for certain use cases.
While building a modern api in FastAPI, one might initially assume JSON is the only required format. However, ignoring XML capabilities could isolate your service from a significant portion of the enterprise landscape. A well-designed API Developer Portal must cater to all potential consumers, including those operating within XML-centric environments. Therefore, understanding how to gracefully handle and meticulously document XML responses within your FastAPI services is not just a technical exercise; it's a strategic imperative for ensuring broad interoperability, maximizing adoption, and future-proofing your api for diverse integration needs. By doing so, you ensure your api can communicate effectively with the past, present, and future of interconnected systems.
Strategies for Representing XML in FastAPI Responses
Documenting XML responses in FastAPI requires a nuanced approach, as FastAPI's automatic OpenAPI generation is primarily geared towards JSON Schema. The key is to leverage OpenAPI's flexibility to explicitly describe the XML content type and provide clear examples of its structure. Here, we'll explore several strategies, from the most basic to more advanced techniques.
Method 1: Using FastAPI's Response Class with media_type="application/xml"
The most straightforward way to return XML from a FastAPI endpoint is to use the built-in fastapi.responses.Response class and explicitly set its media_type to application/xml.
Basic Example:
from fastapi import FastAPI
from fastapi.responses import Response
app = FastAPI()
@app.get("/techblog/en/xml/raw", tags=["XML Responses"])
async def get_raw_xml_response():
xml_content = """<?xml version="1.0" encoding="UTF-8"?>
<root>
<message>Hello from raw XML!</message>
<timestamp>2023-10-27T10:00:00Z</timestamp>
</root>"""
return Response(content=xml_content, media_type="application/xml")
Limitations:
While this method successfully sends an XML response to the client, its representation in the automatically generated OpenAPI documentation (Swagger UI/ReDoc) is very basic. FastAPI, by default, will see that you're returning a Response object and will likely just document the response body as a generic string for application/xml. It won't provide any structural details about the XML itself. This can be highly unhelpful for developers trying to consume your api without further out-of-band documentation.
How to Improve Docs for This (without structure):
You can slightly enhance the documentation by using the response_model parameter with a simple str type and adding a description to the path operation, or by explicitly describing the response using the responses parameter in the decorator.
from fastapi import FastAPI
from fastapi.responses import Response
from typing import Dict, Any
app = FastAPI()
@app.get(
"/techblog/en/xml/raw-with-description",
tags=["XML Responses"],
response_model=str, # This tells FastAPI to expect a string, but doesn't detail content
summary="Returns a raw XML string with a basic description.",
description="This endpoint provides a hardcoded XML response for demonstration purposes. The structure is fixed.",
responses={
200: {
"description": "Successful XML Response",
"content": {
"application/xml": {
"schema": {
"type": "string",
"description": "A generic XML document."
}
}
}
}
}
)
async def get_raw_xml_response_with_description():
xml_content = """<?xml version="1.0" encoding="UTF-8"?>
<root>
<message>Hello from described raw XML!</message>
<version>1.0</version>
</root>"""
return Response(content=xml_content, media_type="application/xml")
Even with this improvement, the schema for application/xml is still {"type": "string"}, which gives no insight into the actual XML elements or attributes. This brings us to a more robust solution.
Method 2: Leveraging OpenAPI's examples for application/xml
This is the most common and effective approach for providing rich, structural documentation for XML responses within FastAPI's OpenAPI docs. Instead of trying to force a JSON Schema to describe XML (which is fundamentally at odds), we use the examples field to provide literal XML strings that demonstrate the expected structure.
Deep Dive into OpenAPI Structure:
The responses parameter in a FastAPI path operation allows you to meticulously define the documentation for different HTTP status codes. Within this, you define a content object for each media_type. For application/xml, the key insight is to define a schema (usually type: string for raw XML) and then, critically, include an example or examples object.
from fastapi import FastAPI
from fastapi.responses import Response
from pydantic import BaseModel
import xml.etree.ElementTree as ET # For demonstration of XML generation
app = FastAPI()
# A Pydantic model representing the data that *could* be returned as XML
class Product(BaseModel):
id: str
name: str
price: float
currency: str
# Helper function to convert a Pydantic model to an XML string
def product_to_xml(product: Product) -> str:
root = ET.Element("Product")
ET.SubElement(root, "Id").text = product.id
ET.SubElement(root, "Name").text = product.name
price_elem = ET.SubElement(root, "Price")
price_elem.text = str(product.price)
price_elem.set("currency", product.currency) # Example of an attribute
return ET.tostring(root, encoding="utf-8", xml_declaration=True).decode()
@app.get(
"/techblog/en/xml/product/{product_id}",
tags=["XML Responses"],
summary="Retrieve product details as XML.",
description="Fetches details for a specific product and returns the information formatted as an XML document.",
responses={
200: {
"description": "Successful retrieval of product details in XML format.",
"content": {
"application/xml": {
"schema": {
"type": "string",
"description": "The product details represented as an XML document. Refer to the example for structure."
},
"examples": {
"example1": {
"summary": "Example Product Response",
"value": """<?xml version="1.0" encoding="UTF-8"?>
<Product>
<Id>P001</Id>
<Name>Laptop Pro</Name>
<Price currency="USD">1200.00</Price>
<Description>High-performance laptop for professionals.</Description>
</Product>"""
},
"example2": {
"summary": "Another Product Example",
"value": """<?xml version="1.0" encoding="UTF-8"?>
<Product>
<Id>A005</Id>
<Name>Wireless Mouse</Name>
<Price currency="EUR">45.50</Price>
</Product>"""
}
}
}
}
},
404: {
"description": "Product not found.",
"content": {
"application/xml": {
"schema": {"type": "string"},
"example": """<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>404</Code>
<Message>Product with ID 'PXXX' not found.</Message>
</Error>"""
}
}
}
}
)
async def get_product_xml(product_id: str):
# In a real application, you'd fetch this from a database
if product_id == "P001":
product_data = Product(id="P001", name="Laptop Pro", price=1200.00, currency="USD")
return Response(content=product_to_xml(product_data), media_type="application/xml")
elif product_id == "A005":
product_data = Product(id="A005", name="Wireless Mouse", price=45.50, currency="EUR")
return Response(content=product_to_xml(product_data), media_type="application/xml")
else:
# Example of a 404 XML error response
error_xml = f"""<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>404</Code>
<Message>Product with ID '{product_id}' not found.</Message>
</Error>"""
return Response(content=error_xml, media_type="application/xml", status_code=404)
Explanation:
responses={...}: This dictionary is where you define documentation for various HTTP status codes.200: {...}: Documentation for a successful 200 OK response."content": {"application/xml": {...}}: Specifies that for the 200 OK response, theapplication/xmlmedia type is supported."schema": {"type": "string", "description": "..."}: This part is still necessary. It tells the OpenAPI consumer that theapplication/xmlcontent will be a string. Thedescriptionhere can be used to add a high-level explanation. Crucially, the detailed structure comes next."examples": {"example1": {...}, "example2": {...}}: This is the core of our XML documentation. We provide one or more named examples."summary": "...": A brief title for the example."value": """...""": The literal XML string that demonstrates the expected structure. This is where you put your sample XML, including elements, attributes, and namespaces as they would appear in a real response. Multi-line strings (triple quotes) are very useful here.
Key Advantages:
- Clarity: Developers immediately see the exact XML structure they should expect.
- Completeness: You can include all relevant elements, attributes, and even demonstrate optional fields or different response variations.
- No Magic: It doesn't rely on FastAPI trying to infer XML structure from a JSON Schema, which is prone to errors and limitations.
- Validation Assistance: While not directly validating the XML schema, providing concrete examples aids developers in writing their XML parsing logic correctly.
Using externalValue for Larger Examples:
For very large or complex XML examples, embedding the entire string directly in the decorator can make the code hard to read and maintain. OpenAPI allows you to reference an external file for the example using externalValue.
First, create an example.xml file:
<!-- example.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<DetailedReport>
<Header>
<ReportID>RPT-001-2023</ReportID>
<DateGenerated>2023-10-27T10:30:00Z</DateGenerated>
</Header>
<Items>
<Item ID="123">
<Name>Item A</Name>
<Value>100.00</Value>
</Item>
<Item ID="456">
<Name>Item B</Name>
<Value>250.50</Value>
</Item>
</Items>
<Summary TotalItems="2" GrandTotal="350.50"/techblog/en/>
</DetailedReport>
Then, in your FastAPI code, you'd reference it (though FastAPI's decorator directly supports value, externalValue might require a bit more manual OpenAPI manipulation or a custom function to load the schema). For direct use in FastAPI, loading the file content into the value field is generally simpler:
# Assuming example.xml is in the same directory
import os
current_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(current_dir, "example.xml"), "r", encoding="utf-8") as f:
detailed_report_xml_example = f.read()
@app.get(
"/techblog/en/xml/report",
tags=["XML Responses"],
summary="Retrieves a detailed report as XML from an external example.",
description="This endpoint provides a complex XML report structure documented using an external XML file.",
responses={
200: {
"description": "Detailed XML Report",
"content": {
"application/xml": {
"schema": {"type": "string"},
"examples": {
"DetailedReportExample": {
"summary": "A comprehensive report XML",
"value": detailed_report_xml_example
}
}
}
}
}
}
)
async def get_detailed_report():
# In a real app, you would generate or fetch this XML
return Response(content=detailed_report_xml_example, media_type="application/xml")
This method maintains code readability while still providing comprehensive XML documentation in the OpenAPI output.
Method 3: Customizing the OpenAPI Schema Directly (Advanced)
For scenarios requiring extremely granular control over the OpenAPI definition, or when attempting to describe XML elements and attributes beyond simple examples, you might delve into customizing the OpenAPI schema directly. This is a more advanced technique and generally less common for simply documenting XML responses, as examples usually suffice. However, it's powerful for deep integrations or when trying to bridge XSDs with OpenAPI.
OpenAPI Specification does have an xml object extension within Schema Objects to provide metadata for XML serialization. This xml object is not for describing arbitrary XML structures in the responses section but rather for influencing how a JSON Schema would be serialized to XML if it were capable. In the context of already existing XML responses, its utility for documentation within FastAPI is limited. The examples approach remains superior for showing the actual XML.
However, if you must represent specific XML structural properties, like element names, attributes, or namespaces for your API documentation, and examples don't feel sufficient for some reason, you could manually inject or modify the OpenAPI schema. This often involves using FastAPI's app.openapi_schema attribute after the initial schema generation or overriding app.openapi() to return a custom schema.
Example (Conceptual - generally, examples is better):
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
from fastapi.responses import Response
import xml.etree.ElementTree as ET
app = FastAPI()
# Example Pydantic model (for conceptual mapping)
class MyData(BaseModel):
name: str
value: int
# Define a custom OpenAPI schema generation function
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="Custom XML Documentation API",
version="1.0.0",
routes=app.routes,
)
# Manually add or modify a response definition for an XML endpoint
# This is highly specific and requires deep knowledge of OpenAPI structure
# For instance, if you want to define a "component" that *could* be XML
# and reference it, it's typically still done with 'type: string' and examples.
# The 'xml' object within a schema component is primarily for JSON -> XML mapping.
# Let's say we have an endpoint '/xml/structured' that returns a specific XML structure.
# We might want to add a custom schema component that *describes* this XML
# using descriptive properties, even if it's eventually rendered as a string.
# This is mostly illustrative of *where* you'd manipulate it,
# not necessarily a recommended pattern for simple XML responses.
if "/techblog/en/xml/structured" in openapi_schema["paths"]:
structured_response_path = openapi_schema["paths"]["/techblog/en/xml/structured"]["get"]["responses"]["200"]
if "application/xml" in structured_response_path["content"]:
# This is where you could manually add more details,
# though it would still likely fallback to string type in schema
# and rely on 'examples' for true structure.
# Example: Adding specific XML properties for attributes or element names
# This 'xml' object in OAS is intended to specify how a JSON schema
# maps to XML, not to *describe* an existing arbitrary XML.
# For direct XML description, 'examples' is pragmatic.
structured_response_path["content"]["application/xml"]["schema"] = {
"type": "string",
"description": "A custom structured XML document. Attributes like 'id' and 'type' are present.",
"example": """<?xml version="1.0" encoding="UTF-8"?>
<DataItem id="123" type="config">
<Name>Configuration Item</Name>
<Value>True</Value>
</DataItem>"""
}
# The above is effectively just updating the example and description,
# not adding a true XML schema definition within OpenAPI.
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi # Assign the custom function
@app.get(
"/techblog/en/xml/structured",
tags=["XML Responses"],
summary="Returns a structured XML response (documented via custom OpenAPI schema).",
description="This endpoint demonstrates how you *could* influence the OpenAPI schema to include more specific XML examples, "
"even though for actual XML structure, 'examples' remain the most effective tool.",
)
async def get_structured_xml_response():
xml_content = """<?xml version="1.0" encoding="UTF-8"?>
<DataItem id="123" type="config">
<Name>Configuration Item</Name>
<Value>True</Value>
</DataItem>"""
return Response(content=xml_content, media_type="application/xml")
This method highlights that while you can override app.openapi, for documenting XML, the examples approach from Method 2 is generally more practical and less prone to errors or misinterpretations of the OpenAPI Specification's intent regarding xml objects within schemas. The xml object in OpenAPI is for mapping JSON schema to XML, not for defining XML schema itself. For arbitrary XML, the best approach for clear documentation remains providing representative XML string examples.
Important Considerations:
- Serialization/Deserialization: FastAPI's default is JSON. If your endpoint is meant to return XML, you must manually serialize your Python data structures (dictionaries, Pydantic model instances) into an XML string before returning them in a
Responseobject withmedia_type="application/xml". Similarly, for receiving XML, you'd need a custom body parser, which is a more advanced topic not covered in depth here. - Libraries for XML Handling:
xml.etree.ElementTree(built-in): Good for basic XML creation and parsing.lxml: A powerful, fast, and feature-rich library for XML and HTML processing, offering more advanced capabilities like XPath, XSLT, and schema validation. Highly recommended for complex XML work.dicttoxml: A third-party library that can convert Python dictionaries directly into XML strings, simplifying serialization.
- Namespace Handling: XML namespaces are crucial for avoiding naming collisions in complex documents. If your XML responses use namespaces, ensure your examples accurately reflect them (e.g.,
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">). - Schema vs. Example: Always remember the distinction: OpenAPI's
schemaforapplication/xmlprimarily indicates "this is a string" (or potentially an object that can be represented as XML if you're using advanced schema mapping tools), but the actual, specific XML structure is best conveyed throughexamples. Don't try to cram XML schema definition into JSON Schema.
By understanding these strategies and considerations, you can confidently document your XML-producing FastAPI endpoints, making them as discoverable and easy to consume as their JSON counterparts within your OpenAPI-driven API Developer Portal.
APIPark is a high-performance AI gateway that allows you to securely access the most comprehensive LLM APIs globally on the APIPark platform, including OpenAI, Anthropic, Mistral, Llama2, Google Gemini, and more.Try APIPark now! πππ
Practical Implementation: A Step-by-Step Guide with FastAPI Code
Let's consolidate our understanding into a practical FastAPI application that demonstrates how to serve and, more importantly, document XML responses effectively. We'll set up a simple API for managing books, with some endpoints returning XML.
1. Setting Up a Basic FastAPI Application
First, ensure you have FastAPI and Uvicorn installed:
pip install fastapi "uvicorn[standard]" python-multipart lxml dicttoxml
python-multipart is included as a common FastAPI dependency, lxml and dicttoxml for robust XML handling.
Now, create a file named main.py:
# main.py
from fastapi import FastAPI, HTTPException
from fastapi.responses import Response
from pydantic import BaseModel, Field
from typing import List, Optional
import xml.etree.ElementTree as ET
from dicttoxml import dicttoxml # Third-party library for easy dict to XML conversion
app = FastAPI(
title="Book Management API with XML Responses",
description="A demonstration of how to return and document XML responses in FastAPI.",
version="1.0.0"
)
# In-memory database for demonstration
BOOKS_DB = [
{
"id": "b001",
"title": "The Hitchhiker's Guide to the Galaxy",
"author": "Douglas Adams",
"year": 1979,
"isbn": "978-0345391803",
"genre": "Science Fiction"
},
{
"id": "b002",
"title": "1984",
"author": "George Orwell",
"year": 1949,
"isbn": "978-0451524935",
"genre": "Dystopian"
},
{
"id": "b003",
"title": "Pride and Prejudice",
"author": "Jane Austen",
"year": 1813,
"isbn": "978-0141439518",
"genre": "Romance"
}
]
# Pydantic model for a Book
class Book(BaseModel):
id: str = Field(..., description="Unique identifier for the book")
title: str = Field(..., description="The title of the book")
author: str = Field(..., description="The author(s) of the book")
year: int = Field(..., description="The publication year")
isbn: Optional[str] = Field(None, description="International Standard Book Number")
genre: Optional[str] = Field(None, description="Genre of the book")
# --- XML Helper Functions ---
def convert_book_to_xml(book: Book) -> str:
"""Converts a Book Pydantic model instance into an XML string."""
root = ET.Element("Book", id=book.id)
ET.SubElement(root, "Title").text = book.title
ET.SubElement(root, "Author").text = book.author
ET.SubElement(root, "PublicationYear").text = str(book.year)
if book.isbn:
ET.SubElement(root, "ISBN").text = book.isbn
if book.genre:
ET.SubElement(root, "Genre").text = book.genre
# Add an XML declaration for proper XML formatting
return ET.tostring(root, encoding="utf-8", xml_declaration=True).decode()
def convert_books_list_to_xml(books: List[Book]) -> str:
"""Converts a list of Book Pydantic model instances into an XML string."""
root = ET.Element("Books")
for book in books:
book_elem = ET.SubElement(root, "Book", id=book.id)
ET.SubElement(book_elem, "Title").text = book.title
ET.SubElement(book_elem, "Author").text = book.author
ET.SubElement(book_elem, "PublicationYear").text = str(book.year)
if book.isbn:
ET.SubElement(book_elem, "ISBN").text = book.isbn
if book.genre:
ET.SubElement(book_elem, "Genre").text = book.genre
return ET.tostring(root, encoding="utf-8", xml_declaration=True).decode()
def convert_dict_to_xml_response(data: dict, root_tag: str = "Response") -> str:
"""Converts a Python dictionary to a generic XML string using dicttoxml."""
# dicttoxml by default includes an XML declaration and a default root element.
# We can customize the root tag.
xml_bytes = dicttoxml(data, custom_root=root_tag, attr_type=False, return_bytes=True)
return xml_bytes.decode('utf-8')
# --- API Endpoints ---
@app.get(
"/techblog/en/books/json",
response_model=List[Book],
tags=["Books (JSON)"],
summary="Get all books as JSON.",
description="Retrieves a list of all available books, returned in standard JSON format. This endpoint "
"serves as a comparison to the XML endpoints."
)
async def get_all_books_json():
"""
Retrieves a list of all available books.
"""
return BOOKS_DB
@app.get(
"/techblog/en/books/xml",
tags=["Books (XML)"],
summary="Get all books as XML.",
description="Retrieves a list of all available books, formatted as an XML document. "
"Each book entry includes its ID as an attribute and other details as child elements.",
responses={
200: {
"description": "Successful retrieval of all books in XML format.",
"content": {
"application/xml": {
"schema": {
"type": "string",
"description": "An XML document containing a list of books. The root element is `<Books>`, "
"and each book is represented by a `<Book>` element with an 'id' attribute."
},
"examples": {
"booksListExample": {
"summary": "Example list of books XML",
"value": """<?xml version="1.0" encoding="UTF-8"?>
<Books>
<Book id="b001">
<Title>The Hitchhiker's Guide to the Galaxy</Title>
<Author>Douglas Adams</Author>
<PublicationYear>1979</PublicationYear>
<ISBN>978-0345391803</ISBN>
<Genre>Science Fiction</Genre>
</Book>
<Book id="b002">
<Title>1984</Title>
<Author>George Orwell</Author>
<PublicationYear>1949</PublicationYear>
<ISBN>978-0451524935</ISBN>
<Genre>Dystopian</Genre>
</Book>
</Books>"""
}
}
}
}
}
}
)
async def get_all_books_xml():
"""
Retrieves a list of all available books as an XML document.
"""
books_data = [Book(**book_dict) for book_dict in BOOKS_DB]
xml_content = convert_books_list_to_xml(books_data)
return Response(content=xml_content, media_type="application/xml")
@app.get(
"/techblog/en/books/xml/{book_id}",
tags=["Books (XML)"],
summary="Get a single book by ID as XML.",
description="Retrieves details for a specific book identified by its ID, formatted as an XML document. "
"If the book is not found, an XML error message is returned with a 404 status.",
responses={
200: {
"description": "Successful retrieval of book details in XML format.",
"content": {
"application/xml": {
"schema": {
"type": "string",
"description": "An XML document containing the details of a single book. "
"The root element is `<Book>` with an 'id' attribute."
},
"examples": {
"singleBookExample": {
"summary": "Example single book XML",
"value": """<?xml version="1.0" encoding="UTF-8"?>
<Book id="b001">
<Title>The Hitchhiker's Guide to the Galaxy</Title>
<Author>Douglas Adams</Author>
<PublicationYear>1979</PublicationYear>
<ISBN>978-0345391803</ISBN>
<Genre>Science Fiction</Genre>
</Book>"""
}
}
}
}
},
404: {
"description": "Book not found.",
"content": {
"application/xml": {
"schema": {"type": "string"},
"example": """<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>BOOK_NOT_FOUND</Code>
<Message>The requested book with ID 'unknown' was not found in the catalog.</Message>
</Error>"""
}
}
}
}
)
async def get_book_xml_by_id(book_id: str):
"""
Retrieves details for a specific book by its ID as an XML document.
"""
book_data = next((book for book in BOOKS_DB if book["id"] == book_id), None)
if book_data is None:
error_dict = {
"Error": {
"Code": "BOOK_NOT_FOUND",
"Message": f"The requested book with ID '{book_id}' was not found in the catalog."
}
}
xml_error_content = convert_dict_to_xml_response(error_dict, root_tag=None) # dicttoxml adds default root if not specified
return Response(content=xml_error_content, media_type="application/xml", status_code=404)
book_pydantic = Book(**book_data)
xml_content = convert_book_to_xml(book_pydantic)
return Response(content=xml_content, media_type="application/xml")
@app.post(
"/techblog/en/books/add-xml",
tags=["Books (XML)"],
summary="Add a new book (accepts XML request body, returns XML confirmation).",
description="This endpoint demonstrates processing an XML request body to add a new book. "
"The confirmation of addition is also returned in XML format. "
"Note: FastAPI's automatic request body parsing is JSON-centric; for XML input, "
"we manually extract and parse the request body string.",
responses={
200: {
"description": "Book successfully added (XML confirmation).",
"content": {
"application/xml": {
"schema": {"type": "string"},
"examples": {
"confirmationExample": {
"summary": "XML Confirmation",
"value": """<?xml version="1.0" encoding="UTF-8"?>
<Confirmation>
<Status>Success</Status>
<Message>Book 'New Title' (id: BXYZ) added successfully.</Message>
</Confirmation>"""
}
}
}
}
},
400: {
"description": "Invalid XML or missing data.",
"content": {
"application/xml": {
"schema": {"type": "string"},
"example": """<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>INVALID_XML_DATA</Code>
<Message>Failed to parse XML or required fields are missing.</Message>
</Error>"""
}
}
}
},
# To document the request body for XML, we use the `requestBody` key in `openapi_extra`.
# This is an advanced manual OpenAPI definition.
openapi_extra={
"requestBody": {
"content": {
"application/xml": {
"schema": {
"type": "string",
"description": "An XML document representing the new book to be added. "
"Must contain `<Title>`, `<Author>`, and `<PublicationYear>` elements. "
"Optional fields include `<ISBN>` and `<Genre>`."
},
"examples": {
"addBookRequestExample": {
"summary": "Example XML Request Body for adding a book",
"value": """<?xml version="1.0" encoding="UTF-8"?>
<NewBook>
<Title>The Lord of the Rings</Title>
<Author>J.R.R. Tolkien</Author>
<PublicationYear>1954</PublicationYear>
<ISBN>978-0618260274</ISBN>
<Genre>Fantasy</Genre>
</NewBook>"""
}
}
}
},
"required": True,
"description": "XML payload for adding a new book."
}
}
)
async def add_book_xml(request_body: str):
"""
Adds a new book by accepting an XML request body.
"""
try:
# Manually parse the XML request body
root = ET.fromstring(request_body)
if root.tag != "NewBook":
raise ValueError("Root element must be <NewBook>")
# Extract data
title = root.find("Title").text
author = root.find("Author").text
year = int(root.find("PublicationYear").text)
isbn = root.find("ISBN").text if root.find("ISBN") is not None else None
genre = root.find("Genre").text if root.find("Genre") is not None else None
# Generate a simple ID for the new book
new_id = f"b{len(BOOKS_DB) + 1:03d}"
new_book_data = {
"id": new_id,
"title": title,
"author": author,
"year": year,
"isbn": isbn,
"genre": genre
}
BOOKS_DB.append(new_book_data)
confirmation_dict = {
"Confirmation": {
"Status": "Success",
"Message": f"Book '{title}' (id: {new_id}) added successfully."
}
}
xml_confirmation_content = convert_dict_to_xml_response(confirmation_dict)
return Response(content=xml_confirmation_content, media_type="application/xml")
except (ET.ParseError, AttributeError, ValueError) as e:
error_dict = {
"Error": {
"Code": "INVALID_XML_DATA",
"Message": f"Failed to parse XML or required fields are missing: {e}"
}
}
xml_error_content = convert_dict_to_xml_response(error_dict, root_tag=None)
return Response(content=xml_error_content, media_type="application/xml", status_code=400)
To run this application, save the code as main.py and execute:
uvicorn main:app --reload
Then, open your browser to http://127.0.0.1:8000/docs to see the interactive API documentation.
Exploring the Documentation in Swagger UI/ReDoc
When you navigate to http://127.0.0.1:8000/docs, you will see:
/books/json: This endpoint will show a typical JSON response with theBookPydantic model schema clearly defined./books/xml: This endpoint will showapplication/xmlas a response type. Underneath, instead of a generic string, you'll see an "Example Value" section (or similar, depending on Swagger UI version) with the precisely formatted XML you provided in theexamplesfield. This gives API consumers a clear visual representation of the expected XML structure, including elements like<Books>,<Book>, attributes likeid, and child elements like<Title>,<Author>, etc./books/xml/{book_id}: Similar to the above, it will show an XML example for a successful 200 response and also a distinct XML error example for the 404 response. This is crucial for comprehensive error handling documentation./books/add-xml: This endpoint is particularly interesting. It demonstrates not only documenting an XML response but also an XML request body. Notice the use ofopenapi_extrato manually inject therequestBodydefinition forapplication/xml, complete with its own example. This is a common pattern when FastAPI's automatic inference doesn't cover your specific needs (like non-JSON request bodies).
Table: Comparison of XML Generation Methods and Documentation Approach
Understanding the different ways to generate XML and how they impact documentation can help you choose the right tool for the job.
| Feature / Method | Raw XML String Return (Response(content=xml_str)) |
Python Dict to XML Library (dicttoxml) |
xml.etree.ElementTree (Manual Building) |
|---|---|---|---|
| Ease of Use | Very High (for static, simple XML) | High (for dict-like structures) | Moderate (requires element-by-element build) |
| Dynamic Content Generation | Low (requires string formatting for variables) | High (natively handles dict values) | High (programmatic control) |
| Maintainability | Low (XML structure embedded in code) | High (separates data from structure logic) | Moderate (structure defined in build logic) |
| Pydantic Model Integration | Indirect (models for data, but XML is string) | Good (convert model.dict() to XML) | Good (convert model to ET elements) |
| Control over XML Structure | Direct (you write the exact XML) | Good (via custom_root, attr_type, etc.) |
Excellent (full control over elements, attributes, namespaces) |
| Error Handling | Manual | Library handles basic errors, custom for complex | Manual via try/except |
| Documentation Approach | responses + content: application/xml + example (manual XML string) |
responses + content: application/xml + example (manual XML string) |
responses + content: application/xml + example (manual XML string) |
| Best For | Static or very simple XML responses, quick demos | Dynamic, flat/nested dictionary-like XML data | Complex, hierarchical XML with namespaces, strict schema adherence |
For comprehensive API management, ensuring all formats, including XML, are discoverable and usable through a robust APIPark or similar API Developer Portal is paramount. Platforms like APIPark enhance the developer experience by providing a centralized hub for all API documentation, making it easier for consumers to find, understand, and integrate your services, regardless of the underlying data format or complexity. By adhering to meticulous documentation practices within FastAPI, you lay a solid foundation that platforms like APIPark can then amplify for superior discoverability and governance.
Content-Type Header Implications for Clients
It's vital to remember that when your FastAPI application returns Response(content=xml_content, media_type="application/xml"), it automatically sets the Content-Type HTTP header to application/xml. Clients consuming this api must be prepared to receive and parse XML data, and they might indicate their preference using the Accept header in their requests. While FastAPI's routing and response handling manage the server-side, clear documentation is the client's guide. The examples in the OpenAPI docs serve as a contract, indicating precisely what to expect for application/xml.
This practical guide demonstrates that with a clear understanding of OpenAPI's examples field and some careful crafting of your FastAPI code, you can achieve highly effective and user-friendly documentation for your XML responses, significantly improving the overall developer experience of your API.
Advanced Topics and Best Practices for XML Documentation
While providing concrete XML examples is the cornerstone of effective documentation, several advanced considerations and best practices can further enhance the robustness and usability of your XML-centric FastAPI apis.
Content Negotiation: Catering to Diverse Client Needs
Content negotiation is the process by which a client and server agree on the best representation of a resource. Clients typically indicate their preferred media types using the Accept HTTP header (e.g., Accept: application/json, application/xml;q=0.9, */*;q=0.8). While FastAPI defaults to JSON, you can implement content negotiation to serve different formats from the same endpoint, simplifying your api design and catering to a wider range of clients.
To implement content negotiation in FastAPI, you'd typically inspect the Accept header and return the appropriate Response type.
from fastapi import Request
from fastapi.responses import JSONResponse
@app.get(
"/techblog/en/books/{book_id}",
tags=["Content Negotiation"],
summary="Get book details (JSON or XML via Content Negotiation)",
description="Retrieves book details, returning JSON by default or XML if requested via the Accept header. "
"This demonstrates how a single endpoint can serve multiple formats based on client preference.",
responses={
200: {
"description": "Successful retrieval of book details.",
"content": {
"application/json": {
"schema": Book.schema(), # FastAPI generates this from Pydantic model
"examples": {
"jsonExample": {
"summary": "Example Book JSON",
"value": {
"id": "b001",
"title": "The Hitchhiker's Guide to the Galaxy",
"author": "Douglas Adams",
"year": 1979
}
}
}
},
"application/xml": {
"schema": {"type": "string"},
"examples": {
"xmlExample": {
"summary": "Example Book XML",
"value": """<?xml version="1.0" encoding="UTF-8"?>
<Book id="b001">
<Title>The Hitchhiker's Guide to the Galaxy</Title>
<Author>Douglas Adams</Author>
<PublicationYear>1979</PublicationYear>
</Book>"""
}
}
}
}
},
404: {
"description": "Book not found."
}
}
)
async def get_book_negotiated(book_id: str, request: Request):
book_data = next((book for book in BOOKS_DB if book["id"] == book_id), None)
if book_data is None:
raise HTTPException(status_code=404, detail="Book not found")
accept_header = request.headers.get("Accept")
if accept_header and "application/xml" in accept_header:
book_pydantic = Book(**book_data)
xml_content = convert_book_to_xml(book_pydantic)
return Response(content=xml_content, media_type="application/xml")
else:
# Default to JSON or if application/json is explicitly requested
return JSONResponse(content=book_data) # FastAPI will handle JSON serialization
In the documentation, this endpoint will then clearly show both application/json and application/xml as possible response types, each with its own schema (Pydantic for JSON, string with example for XML). This explicit documentation for both formats is invaluable for consumers of your api.
Error Handling with XML: Consistent Error Responses
Just as important as documenting successful XML responses is documenting XML error responses. When an error occurs (e.g., 400 Bad Request, 404 Not Found, 500 Internal Server Error), your api should return a consistent error structure, and this structure should also be documented with XML examples.
We already saw an example of a 404 XML error in the /books/xml/{book_id} endpoint. The principle is the same: * Define a generic XML error structure that your api will consistently use. * For each relevant error status code in your endpoint's responses dictionary, add a content entry for application/xml. * Provide a concrete example of an XML error response for that specific status code.
This ensures clients can reliably parse and interpret error messages, regardless of whether they expected a successful JSON or XML response. A good API Developer Portal will highlight these error structures as part of its core documentation.
Schema-First Approach (for XML) and OpenAPI Reflection
If you are working in an environment where XML Schema Definitions (XSDs) are the canonical source of truth for your XML structures, you might be wondering how to "reflect" that XSD into your OpenAPI documentation. OpenAPI is not designed to directly embed or validate XSDs. Its schema object is JSON Schema. However, you can achieve a degree of "schema-first" by:
- Providing
descriptionandexternalDocs: In the OpenAPIschemaobject forapplication/xml, you can use thedescriptionfield to refer to the canonical XSD and include anexternalDocsURL pointing to where the XSD is hosted.python "content": { "application/xml": { "schema": { "type": "string", "description": "This XML response conforms to the 'BookCatalog.xsd' schema. See external documentation for details.", "externalDocs": { "description": "Book Catalog XML Schema Definition", "url": "https://example.com/schemas/BookCatalog.xsd" } }, "examples": { "exampleBook": { "summary": "Sample XML conforming to schema", "value": """<!-- XML content here -->""" } } } }This informs developers where to find the authoritative schema. - Manually mirroring structure in
examples: Even with an XSD, you'll still rely heavily on providingexamplesthat perfectly conform to your XSD. These examples are the most direct way for developers to understand the required XML structure without needing to parse an XSD themselves.
Using lxml for Validation: While not directly related to documentation, if your api receives XML and you have an XSD, you should use a library like lxml to validate incoming XML against that XSD. This enforces data integrity at the API gateway level.```python from lxml import etree
Assume book_schema.xsd is a file containing your XSD
with open("book_schema.xsd", "rb") as f: xmlschema_doc = etree.parse(f) book_xmlschema = etree.XMLSchema(xmlschema_doc)async def validate_xml_request_body(xml_data: str): try: parsed_xml = etree.fromstring(xml_data) book_xmlschema.assertValid(parsed_xml) return True except etree.XMLSyntaxError as e: # Handle XML parsing errors raise HTTPException(status_code=400, detail=f"Invalid XML syntax: {e}") except etree.DocumentInvalid as e: # Handle XML schema validation errors raise HTTPException(status_code=400, detail=f"XML does not conform to schema: {e}") ```You would then call this validator within your FastAPI endpoint that accepts XML.
Custom XML Serializers/Renderers: Beyond Simple Strings
For highly complex scenarios, where xml.etree or dicttoxml might not provide enough flexibility, or you need to integrate with a custom ORM that outputs specific XML formats, you might consider creating a custom FastAPI Response class or a custom Response handler. This allows you to encapsulate complex XML generation logic.
For instance, a custom XMLResponse class that takes a Pydantic model and automatically serializes it using a predefined XSLT or template. However, for most cases, the helper functions demonstrated in the practical example are sufficient.
Importance of API Developer Portal
All these technical considerations ultimately serve a singular goal: to make your api consumable. This is where an API Developer Portal becomes indispensable. A well-maintained portal acts as the central hub for all api documentation, including detailed OpenAPI specifications, usage guides, tutorials, and examples.
When your FastAPI application generates an OpenAPI document that meticulously details XML responses with examples, this rich information is directly ingestible by an API Developer Portal. The portal can then render these details beautifully, providing an interactive, searchable interface for developers. This includes:
- Displaying the
application/xmlmedia type clearly for relevant endpoints. - Allowing developers to view and copy the provided XML
examples. - Highlighting
externalDocslinks to XSDs or other relevant resources. - Presenting the consistent XML error structures.
A robust platform like APIPark, an open-source AI gateway and API management platform, excels in centralizing and managing diverse API documentation. By simplifying API lifecycle management, including various response formats like XML, APIPark ensures that your painstakingly documented FastAPI XML endpoints are not only technically sound but also easily discoverable and usable by developers, whether they are integrating with modern JSON-based services or established XML-driven systems. Such a platform transforms raw documentation into a powerful tool for API adoption and seamless integration.
By embracing these advanced topics and best practices, you move beyond mere functionality to deliver a truly robust, well-documented, and developer-friendly api that supports both JSON and XML paradigms, maximizing its interoperability and value in diverse enterprise environments.
Conclusion: Bridging the Gap for Comprehensive API Design
The journey to effectively represent XML responses within FastAPI's documentation, while seemingly niche in a JSON-dominated era, is a testament to the real-world complexities of modern API development. We've traversed the landscape from understanding FastAPI's OpenAPI-driven core to acknowledging the enduring, critical role of XML in various enterprise and industry-specific contexts. By meticulously applying the strategies outlined β particularly leveraging the examples field within the OpenAPI content object for application/xml media types β developers can bridge the documentation gap, transforming generic string descriptions into rich, illustrative XML structures.
This guide emphasized that while FastAPI, by design, champions JSON, its underlying OpenAPI Specification provides the flexibility to accommodate alternative formats. The key insight lies in recognizing that for XML, descriptive examples, rather than JSON Schema-based definitions, are the most pragmatic and effective means of conveying structural information. Whether you're dealing with legacy systems, adhering to strict industry standards, or simply providing a comprehensive data exchange option, the ability to clearly document XML output is invaluable.
The practical examples, ranging from simple XML string returns to dynamic XML generation and even documenting XML request bodies, underscore the power of explicit configuration in FastAPI. Furthermore, delving into advanced topics like content negotiation, consistent XML error handling, and the reflection of external XML Schemas (XSDs) into documentation highlights a commitment to robust, future-proof api design.
Ultimately, the goal is to create apis that are not only functional but also supremely usable. Accurate and comprehensive documentation, irrespective of data format, is the cornerstone of this usability. When your FastAPI service is meticulously documented, detailing both JSON and XML response structures, it empowers developers, reduces integration friction, and elevates the overall experience of your API Developer Portal. Platforms like APIPark then amplify these efforts, ensuring that your API, with all its rich and diverse formats, is centrally managed, easily discoverable, and optimally governed, paving the way for broader adoption and deeper integration across the digital ecosystem. By mastering these techniques, you ensure your api speaks all the necessary languages, making it truly inclusive and impactful in the interconnected world.
Frequently Asked Questions (FAQs)
Q1: Why should I bother with XML documentation in FastAPI if JSON is so prevalent?
A1: While JSON is dominant for many modern applications, XML remains crucial in specific sectors like finance, healthcare (HL7), government, and large enterprises due to legacy systems, strict industry standards, and requirements for robust schema validation (XSD) and digital signatures. Documenting XML ensures your API is interoperable with these systems, expanding its potential consumer base and utility, especially within a diverse API Developer Portal.
Q2: Can FastAPI automatically generate XML schemas from Pydantic models in its OpenAPI documentation?
A2: No, FastAPI's automatic schema generation is based on JSON Schema, which is the native language for describing data structures within the OpenAPI Specification. While you can indicate that an application/xml media type is returned, FastAPI will not automatically infer the XML structure from your Pydantic model. For detailed XML documentation, you must provide explicit XML examples using the examples field within the responses definition in your path operation.
Q3: What's the best way to return XML from a FastAPI endpoint?
A3: The most common and recommended way is to serialize your Python data (e.g., a dictionary or Pydantic model instance) into an XML string using a library like xml.etree.ElementTree (built-in) or dicttoxml (third-party), and then return it wrapped in a fastapi.responses.Response object with media_type="application/xml". This ensures the correct Content-Type header is sent to the client.
Q4: How do I document XML error responses in FastAPI's OpenAPI docs?
A4: Similar to documenting successful XML responses, for error responses (e.g., 400, 404, 500), you should explicitly define the content for application/xml under the corresponding HTTP status code in your endpoint's responses dictionary. Crucially, include an example field containing a sample XML string that demonstrates your API's consistent error structure. This helps clients understand and parse error messages effectively.
Q5: Is it possible to handle both JSON and XML requests/responses from a single FastAPI endpoint?
A5: Yes, this is achievable through content negotiation. On the response side, you can inspect the client's Accept header and return either JSONResponse or Response(content=xml_string, media_type="application/xml") accordingly. For the request body, FastAPI will automatically parse JSON, but for XML requests, you'd typically accept the raw request body as a string, then manually parse and validate the XML content within your endpoint. Both JSON and XML options should be meticulously documented in the OpenAPI specification for that endpoint, detailing their respective schemas and examples.
πYou can securely and efficiently call the OpenAI API on APIPark in just two steps:
Step 1: Deploy the APIPark AI gateway in 5 minutes.
APIPark is developed based on Golang, offering strong product performance and low development and maintenance costs. You can deploy APIPark with a single command line.
curl -sSO https://download.apipark.com/install/quick-start.sh; bash quick-start.sh

In my experience, you can see the successful deployment interface within 5 to 10 minutes. Then, you can log in to APIPark using your account.

Step 2: Call the OpenAI API.
