<p dir="auto">Why doesn´t FastAPI use python hints for returning types as well?</p>
<p dir="auto">This code is more intuitive</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="@app.post(&quot;/items/&quot;)
async def create_item(item: Item) -&gt; Item:
    return item"><pre class="notranslate"><span class="pl-en">@<span class="pl-s1">app</span>.<span class="pl-en">post</span>(<span class="pl-s">"/items/"</span>)</span>
<span class="pl-k">async</span> <span class="pl-k">def</span> <span class="pl-en">create_item</span>(<span class="pl-s1">item</span>: <span class="pl-v">Item</span>) <span class="pl-c1">-&gt;</span> <span class="pl-v">Item</span>:
    <span class="pl-k">return</span> <span class="pl-s1">item</span></pre></div>
<p dir="auto">than this</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="@app.post(&quot;/items/&quot;, response_model=Item)
async def create_item(item: Item):
    return item"><pre class="notranslate"><span class="pl-en">@<span class="pl-s1">app</span>.<span class="pl-en">post</span>(<span class="pl-s">"/items/"</span>, <span class="pl-s1">response_model</span><span class="pl-c1">=</span><span class="pl-v">Item</span>)</span>
<span class="pl-k">async</span> <span class="pl-k">def</span> <span class="pl-en">create_item</span>(<span class="pl-s1">item</span>: <span class="pl-v">Item</span>):
    <span class="pl-k">return</span> <span class="pl-s1">item</span></pre></div>
<h3 dir="auto">First Check</h3>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I added a very descriptive title to this issue.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I used the GitHub search to find a similar issue and didn't find it.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I searched the FastAPI documentation, with the integrated search.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I already searched in Google "How to X in FastAPI" and didn't find any information.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I already read and followed all the tutorial in the docs and didn't find an answer.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I already checked if it is not related to FastAPI but to <a href="https://github.com/samuelcolvin/pydantic">Pydantic</a>.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I already checked if it is not related to FastAPI but to <a href="https://github.com/swagger-api/swagger-ui">Swagger UI</a>.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I already checked if it is not related to FastAPI but to <a href="https://github.com/Redocly/redoc">ReDoc</a>.</li>
</ul>
<h3 dir="auto">Commit to Help</h3>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I commit to help with one of those options <g-emoji class="g-emoji" alias="point_up_2" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f446.png">👆</g-emoji></li>
</ul>
<h3 dir="auto">Example Code</h3>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="# see &quot;Wanted Code&quot; section"><pre class="notranslate"><span class="pl-c"># see "Wanted Code" section</span></pre></div>
<h3 dir="auto">Description</h3>
<p dir="auto">Media type versioning is a recommended practice for versioning a RESTful API across the industry: Zalando <a href="https://opensource.zalando.com/restful-api-guidelines/#114" rel="nofollow">recommends it</a>, there are <a href="https://www.narwhl.com/2015/03/the-ultimate-solution-to-versioning-rest-apis-content-negotiation/" rel="nofollow">various</a> <a href="https://docs.microsoft.com/en-us/archive/msdn-magazine/2014/february/cutting-edge-content-negotiation-and-web-api-for-the-asp-net-mvc-developer" rel="nofollow">articles</a> <a href="https://www.linkedin.com/pulse/api-versioning-best-practices-muhammed-mokhtar" rel="nofollow">that</a> endorse it. And overall it seems like a very good and flexible solution for versioning APIs:</p>
<ul dir="auto">
<li>It allows versioning individual API endpoints when you need it.</li>
<li>Even better, you can version input and output schemas independently.</li>
<li>It allows the rollout of backward-incompatible changes in a backward compatible manner without bending the rules of RESTful API semantics, i.e. without the need to invent a new "object type" to put a new version of the endpoint on a separate URL.</li>
<li>It can be fully described with an OpenAPI schema, e.g. attached <a href="https://github.com/tiangolo/fastapi/files/8283734/openapi.json.zip"><code class="notranslate">openapi.json.zip</code></a>.</li>
</ul>
<p dir="auto">These articles I linked above further explain what I think "Media type versioning" means.</p>
<p dir="auto">Other good descriptions about what I mean by "media type versioning" can be found here:</p>
<ul dir="auto">
<li><a href="https://www.narwhl.com/2015/03/the-ultimate-solution-to-versioning-rest-apis-content-negotiation/" rel="nofollow">The Ultimate Solution to Versioning REST APIs: Content Negotiation</a></li>
<li><a href="https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design#media-type-versioning" rel="nofollow">API Design: Media Type Versioning</a></li>
</ul>
<p dir="auto">Some of these pages describe only versioning the request or response object, but ideally both should be supported.</p>
<h3 dir="auto">Wanted Solution</h3>
<ul dir="auto">
<li>Ability to specify multiple request and response models tied to their respecful content types. (See Wanted Code.)</li>
<li>Correct generation of the OpenAPI schema based on that. (An example OpenAPI schema is attached to this issue.)</li>
<li>Parsing of a correct request model based on the request Content-Type.</li>
<li>A more streamlined access to the "Accept" header than inspecting <code class="notranslate">Request.headers</code></li>
<li>Automatic setting of the response "Content-Type" header based on the model returned by path operation function.</li>
<li>The same validation of the returned model as what happens right now for unversioned endpoints.</li>
</ul>
<p dir="auto">With a little bit of your direction, I would be thrilled to make a contribution or create a supporting package for this behavior.</p>
<p dir="auto">If this can be a supporting package, I need a little bit of guidance about how to intercept current flow of schema generation, request parsing and response validation from external code. I can use the code from <code class="notranslate">InferringRouter</code> in <code class="notranslate">fastapi-utils</code> to learn some of this, but any other high-level pointers would be greatly appreciated.</p>
<p dir="auto">If you'd prefer contribution, then I would start from an overall discussion on how you see it done, with couple of references to source code.</p>
<h3 dir="auto">Wanted Code</h3>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="&quot;&quot;&quot;
Example implementation of the API versioning using media types.
&quot;&quot;&quot;

from fastapi import APIRouter, Body, HTTPException, Request
from pydantic import BaseModel, Field


class InputModelV1(BaseModel):
    &quot;First version of input data&quot;
    name: str = Field(description=&quot;Name&quot;)
    count: int = Field(description=&quot;Count&quot;)


class InputModelV2(BaseModel):
    &quot;Second version of input data. Name is split, amount is a float now.&quot;
    first_name: str = Field(description=&quot;First name&quot;)
    last_name: str = Field(description=&quot;First name&quot;)
    amount: float = Field(description=&quot;Total amount&quot;)


class OutputModelV1(BaseModel):
    &quot;First version of output data&quot;
    id: int | None = Field(description=&quot;Id&quot;)
    owner: int | None = Field(description=&quot;Owner Id&quot;)


class OutputModelV2(BaseModel):
    &quot;Secnod version of output data. Ids moved to strings, owner name supplied as well.&quot;
    id: str | None = Field(description=&quot;Id&quot;)
    owner_id: str | None = Field(description=&quot;Owner Id&quot;)
    owner_name: str | None = Field(description=&quot;Owner Name&quot;)


router = APIRouter()


@router.post(
    &quot;/test&quot;,
    responses={
        200: {
            &quot;content&quot;: {
                &quot;application/json;my_output_model_v=1&quot;: {&quot;model&quot;: OutputModelV1},
                &quot;application/json;my_output_model_v=2&quot;: {&quot;model&quot;: OutputModelV2},
            },
            &quot;description&quot;: &quot;Return versioned JSON based on either an Accept header or&quot;
            &quot;the version of the input model&quot;,
        }
    },
)
def versioned_endpoint_v1(
    req: Request,
    v1: InputModelV1
    | None = Body(None, media_type=&quot;application/json;my_input_model_v=1&quot;),
    v2: InputModelV2
    | None = Body(None, media_type=&quot;application/json;my_input_model_v=2&quot;),
) -&gt; OutputModelV1 | OutputModelV2 | None:
    &quot;Example endpoint versioned with media types&quot;
    
    # this if block would ideally happen under the hood
    if v1:
        result = do_op_v1(v1)
    elif v2:
        result = do_op_v2(v2)
    else:
        raise HTTPException(status_code=415, detail=&quot;Unknown input version&quot;)

    # this check would ideally be more abstracted   
    if req.headers.get(&quot;accept&quot;) == &quot;application/json;my_output_model_v=1&quot;:
        return to_v1(result)
    elif req.headers.get(&quot;accept&quot;) == &quot;application/json;my_output_model_v=2&quot;:
        return to_v2(result)
    else:
        # this wouldn't work like this, the output type should be checked before
        # running the operation
        raise HTTPException(status_code=406, detail=&quot;Unsupported output version&quot;)"><pre class="notranslate"><span class="pl-s">"""</span>
<span class="pl-s">Example implementation of the API versioning using media types.</span>
<span class="pl-s">"""</span>

<span class="pl-k">from</span> <span class="pl-s1">fastapi</span> <span class="pl-k">import</span> <span class="pl-v">APIRouter</span>, <span class="pl-v">Body</span>, <span class="pl-v">HTTPException</span>, <span class="pl-v">Request</span>
<span class="pl-k">from</span> <span class="pl-s1">pydantic</span> <span class="pl-k">import</span> <span class="pl-v">BaseModel</span>, <span class="pl-v">Field</span>


<span class="pl-k">class</span> <span class="pl-v">InputModelV1</span>(<span class="pl-v">BaseModel</span>):
    <span class="pl-s">"First version of input data"</span>
    <span class="pl-s1">name</span>: <span class="pl-s1">str</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>(<span class="pl-s1">description</span><span class="pl-c1">=</span><span class="pl-s">"Name"</span>)
    <span class="pl-s1">count</span>: <span class="pl-s1">int</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>(<span class="pl-s1">description</span><span class="pl-c1">=</span><span class="pl-s">"Count"</span>)


<span class="pl-k">class</span> <span class="pl-v">InputModelV2</span>(<span class="pl-v">BaseModel</span>):
    <span class="pl-s">"Second version of input data. Name is split, amount is a float now."</span>
    <span class="pl-s1">first_name</span>: <span class="pl-s1">str</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>(<span class="pl-s1">description</span><span class="pl-c1">=</span><span class="pl-s">"First name"</span>)
    <span class="pl-s1">last_name</span>: <span class="pl-s1">str</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>(<span class="pl-s1">description</span><span class="pl-c1">=</span><span class="pl-s">"First name"</span>)
    <span class="pl-s1">amount</span>: <span class="pl-s1">float</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>(<span class="pl-s1">description</span><span class="pl-c1">=</span><span class="pl-s">"Total amount"</span>)


<span class="pl-k">class</span> <span class="pl-v">OutputModelV1</span>(<span class="pl-v">BaseModel</span>):
    <span class="pl-s">"First version of output data"</span>
    <span class="pl-s1">id</span>: <span class="pl-s1">int</span> <span class="pl-c1">|</span> <span class="pl-c1">None</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>(<span class="pl-s1">description</span><span class="pl-c1">=</span><span class="pl-s">"Id"</span>)
    <span class="pl-s1">owner</span>: <span class="pl-s1">int</span> <span class="pl-c1">|</span> <span class="pl-c1">None</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>(<span class="pl-s1">description</span><span class="pl-c1">=</span><span class="pl-s">"Owner Id"</span>)


<span class="pl-k">class</span> <span class="pl-v">OutputModelV2</span>(<span class="pl-v">BaseModel</span>):
    <span class="pl-s">"Secnod version of output data. Ids moved to strings, owner name supplied as well."</span>
    <span class="pl-s1">id</span>: <span class="pl-s1">str</span> <span class="pl-c1">|</span> <span class="pl-c1">None</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>(<span class="pl-s1">description</span><span class="pl-c1">=</span><span class="pl-s">"Id"</span>)
    <span class="pl-s1">owner_id</span>: <span class="pl-s1">str</span> <span class="pl-c1">|</span> <span class="pl-c1">None</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>(<span class="pl-s1">description</span><span class="pl-c1">=</span><span class="pl-s">"Owner Id"</span>)
    <span class="pl-s1">owner_name</span>: <span class="pl-s1">str</span> <span class="pl-c1">|</span> <span class="pl-c1">None</span> <span class="pl-c1">=</span> <span class="pl-v">Field</span>(<span class="pl-s1">description</span><span class="pl-c1">=</span><span class="pl-s">"Owner Name"</span>)


<span class="pl-s1">router</span> <span class="pl-c1">=</span> <span class="pl-v">APIRouter</span>()


<span class="pl-en">@<span class="pl-s1">router</span>.<span class="pl-en">post</span>(</span>
<span class="pl-en">    <span class="pl-s">"/test"</span>,</span>
<span class="pl-en">    <span class="pl-s1">responses</span><span class="pl-c1">=</span>{</span>
<span class="pl-en">        <span class="pl-c1">200</span>: {</span>
<span class="pl-en">            <span class="pl-s">"content"</span>: {</span>
<span class="pl-en">                <span class="pl-s">"application/json;my_output_model_v=1"</span>: {<span class="pl-s">"model"</span>: <span class="pl-v">OutputModelV1</span>},</span>
<span class="pl-en">                <span class="pl-s">"application/json;my_output_model_v=2"</span>: {<span class="pl-s">"model"</span>: <span class="pl-v">OutputModelV2</span>},</span>
<span class="pl-en">            },</span>
<span class="pl-en">            <span class="pl-s">"description"</span>: <span class="pl-s">"Return versioned JSON based on either an Accept header or"</span></span>
<span class="pl-en">            <span class="pl-s">"the version of the input model"</span>,</span>
<span class="pl-en">        }</span>
<span class="pl-en">    },</span>
<span class="pl-en">)</span>
<span class="pl-k">def</span> <span class="pl-en">versioned_endpoint_v1</span>(
    <span class="pl-s1">req</span>: <span class="pl-v">Request</span>,
    <span class="pl-s1">v1</span>: <span class="pl-v">InputModelV1</span>
    <span class="pl-c1">|</span> <span class="pl-c1">None</span> <span class="pl-c1">=</span> <span class="pl-v">Body</span>(<span class="pl-c1">None</span>, <span class="pl-s1">media_type</span><span class="pl-c1">=</span><span class="pl-s">"application/json;my_input_model_v=1"</span>),
    <span class="pl-s1">v2</span>: <span class="pl-v">InputModelV2</span>
    <span class="pl-c1">|</span> <span class="pl-c1">None</span> <span class="pl-c1">=</span> <span class="pl-v">Body</span>(<span class="pl-c1">None</span>, <span class="pl-s1">media_type</span><span class="pl-c1">=</span><span class="pl-s">"application/json;my_input_model_v=2"</span>),
) <span class="pl-c1">-&gt;</span> <span class="pl-v">OutputModelV1</span> <span class="pl-c1">|</span> <span class="pl-v">OutputModelV2</span> <span class="pl-c1">|</span> <span class="pl-c1">None</span>:
    <span class="pl-s">"Example endpoint versioned with media types"</span>
    
    <span class="pl-c"># this if block would ideally happen under the hood</span>
    <span class="pl-k">if</span> <span class="pl-s1">v1</span>:
        <span class="pl-s1">result</span> <span class="pl-c1">=</span> <span class="pl-en">do_op_v1</span>(<span class="pl-s1">v1</span>)
    <span class="pl-k">elif</span> <span class="pl-s1">v2</span>:
        <span class="pl-s1">result</span> <span class="pl-c1">=</span> <span class="pl-en">do_op_v2</span>(<span class="pl-s1">v2</span>)
    <span class="pl-k">else</span>:
        <span class="pl-k">raise</span> <span class="pl-v">HTTPException</span>(<span class="pl-s1">status_code</span><span class="pl-c1">=</span><span class="pl-c1">415</span>, <span class="pl-s1">detail</span><span class="pl-c1">=</span><span class="pl-s">"Unknown input version"</span>)

    <span class="pl-c"># this check would ideally be more abstracted   </span>
    <span class="pl-k">if</span> <span class="pl-s1">req</span>.<span class="pl-s1">headers</span>.<span class="pl-en">get</span>(<span class="pl-s">"accept"</span>) <span class="pl-c1">==</span> <span class="pl-s">"application/json;my_output_model_v=1"</span>:
        <span class="pl-k">return</span> <span class="pl-en">to_v1</span>(<span class="pl-s1">result</span>)
    <span class="pl-k">elif</span> <span class="pl-s1">req</span>.<span class="pl-s1">headers</span>.<span class="pl-en">get</span>(<span class="pl-s">"accept"</span>) <span class="pl-c1">==</span> <span class="pl-s">"application/json;my_output_model_v=2"</span>:
        <span class="pl-k">return</span> <span class="pl-en">to_v2</span>(<span class="pl-s1">result</span>)
    <span class="pl-k">else</span>:
        <span class="pl-c"># this wouldn't work like this, the output type should be checked before</span>
        <span class="pl-c"># running the operation</span>
        <span class="pl-k">raise</span> <span class="pl-v">HTTPException</span>(<span class="pl-s1">status_code</span><span class="pl-c1">=</span><span class="pl-c1">406</span>, <span class="pl-s1">detail</span><span class="pl-c1">=</span><span class="pl-s">"Unsupported output version"</span>)</pre></div>
<h3 dir="auto">Alternatives</h3>
<p dir="auto">Use URL slugs and different path operation functions for versioning.</p>
<p dir="auto">Other alternatives like versioning based on query parameter or a custom header are not compatible with OpenAPI and would have the same issues with multiple models for request/response body.</p>
<h3 dir="auto">Operating System</h3>
<p dir="auto">Windows</p>
<h3 dir="auto">Operating System Details</h3>
<p dir="auto">This feature is OS-agnostic.</p>
<h3 dir="auto">FastAPI Version</h3>
<p dir="auto">0.75.0</p>
<h3 dir="auto">Python Version</h3>
<p dir="auto">3.10.2</p>
<h3 dir="auto">Additional Context</h3>
<p dir="auto">Issue <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="998059480" data-permission-text="Title is private" data-url="https://github.com/tiangolo/fastapi/issues/3910" data-hovercard-type="issue" data-hovercard-url="/tiangolo/fastapi/issues/3910/hovercard" href="https://github.com/tiangolo/fastapi/issues/3910">#3910</a> describes the "Response" part of what I describe in this issue.</p>
<p dir="auto">Here's a screenshot of what the Swagger UI looks like for the example <a href="https://github.com/tiangolo/fastapi/files/8283734/openapi.json.zip"><code class="notranslate">openapi.json.zip</code></a> file I provided:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/1166296/158789677-05f234a0-624f-4fdd-bce2-cdd9c2180e3a.png"><img width="986" alt="image" src="https://user-images.githubusercontent.com/1166296/158789677-05f234a0-624f-4fdd-bce2-cdd9c2180e3a.png" style="max-width: 100%;"></a></p>