DEV Community

Linh Fish Dev
Linh Fish Dev

Posted on Originally published at linhfish.dev

FastAPI: Use Enums for Path Parameters — Endpoints That Only Accept Valid Values in 5 Minutes

Many APIs have path parameters that only accept a few fixed values — an order status, for example: pending, shipped, or delivered. Instead of accepting a str and checking it by hand with if/else, declare the parameter as an Enum that inherits from str and Enum — FastAPI rejects unknown values with a 422 error on its own, generates docs with the list of choices, and the endpoint code no longer needs a branch for invalid values.

The Problem

Suppose you write an endpoint that lists orders by status. The familiar approach is to accept status as a str and branch through each value:

@app.get("/orders/{status}")
def list_orders(status: str):
    if status == "pending":
        return {"orders": ["#1001", "#1002"]}
    elif status == "shipped":
        return {"orders": ["#1003"]}
    elif status == "delivered":
        return {"orders": ["#1004", "#1005"]}
    return {"error": "Trạng thái không hợp lệ"}
Enter fullscreen mode Exit fullscreen mode

This has three weaknesses. First, an unknown value still travels through the endpoint before being rejected, and the client gets a homemade error JSON instead of the framework’s standard error. Second, the auto-generated docs only record status as a string, so API callers cannot know the valid values without reading the code. Third, every endpoint with a category-like parameter repeats the same if/else block, which gets harder to maintain over time.

The Solution

Declare an Enum that inherits from str and Enum, then use it as the parameter’s type:

from enum import Enum

from fastapi import FastAPI

class OrderStatus(str, Enum):
    PENDING = "pending"
    SHIPPED = "shipped"
    DELIVERED = "delivered"

app = FastAPI()

ORDERS = {
    "pending": ["#1001", "#1002"],
    "shipped": ["#1003"],
    "delivered": ["#1004", "#1005"],
}

@app.get("/orders/{status}")
def list_orders(status: OrderStatus):
    return {"status": status.value, "orders": ORDERS[status.value]}
Enter fullscreen mode Exit fullscreen mode

Inheriting from str keeps the Enum value a string when returned as JSON, while inheriting from Enum tells FastAPI the list of valid values. Inside the endpoint, status is guaranteed to be one of the three values, so you can use status.value directly without any extra check.

Verified with TestClient: GET /orders/pending returns 200 with the correct order list; GET /orders/cancelled returns 422 because the value is not in the Enum; GET /orders/PENDING also returns 422 because the comparison is case-sensitive. The docs at /docs show status as a dropdown, so callers just pick a value instead of guessing.

Apply It to Your Project

This pattern fits any parameter that only accepts a fixed set of values: order status, account type, interface language, priority level. The same Enum can be reused for query parameters and across many endpoints, and adding a new value means editing one place. If you want a clearer description in the docs, combine it with Path(description=…) for that parameter.

Related Posts

Frequently Asked Questions

How is Enum different from Literal?

Literal also lets FastAPI reject unknown values, but it is only a type hint attached in place. An Enum is a named type: it is reusable across many endpoints, can carry extra methods, and shows up clearly in the docs as its own schema.

Can I use an integer Enum?

Yes. Declare class Priority(int, Enum) with values 1, 2, 3 — FastAPI validates integers and the docs show the integer type instead of string.

Are Enum values case-sensitive?

Yes. “PENDING” does not match “pending”, so FastAPI returns 422. If you want to accept both forms, normalize the input yourself before comparing, or define extra uppercase values in the Enum.

Can Enum be used for query parameters?

Yes, the declaration is identical: def search(status: OrderStatus = Query(…)). FastAPI validates the query parameter with the same Enum and shows the list of choices in the docs.

💬 Have you tried this in your own project? If you hit an error or know a shorter way to do it, leave a comment below — I read them all and will add the best ideas to the post.

References


Originally published at linhfish.dev.

Top comments (0)