Path Parameters

beginner fastapi path-params validation enum

Path parameters are the dynamic chunks of a URL. In /users/42, the 42 is a path parameter. FastAPI captures them with curly braces in the path string and matches them to function arguments by name.

The basic shape

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    return {"user_id": user_id, "type": type(user_id).__name__}

The name in {user_id} must match the function parameter name. The type hint int does two things:

  1. FastAPI converts the string from the URL into an int before calling the function.
  2. If the conversion fails, FastAPI returns 422 with a clear error — we never see the bad value.
curl http://localhost:8000/users/42
# {"user_id":42,"type":"int"}

curl http://localhost:8000/users/abc
# 422 - {"detail":[{"type":"int_parsing","loc":["path","user_id"],...}]}

Supported types

The usual suspects work out of the box:

  • int/items/5
  • float/prices/19.99
  • str/slug/hello-world (the default; matches any non-slash text)
  • bool/feature/true (accepts “true”, “false”, “1”, “0”, “yes”, “no”)
  • UUID/orders/123e4567-e89b-12d3-a456-426614174000

Validating with Path()

For deeper validation (min/max, regex, etc.) we use the Path() helper:

from fastapi import FastAPI, Path

app = FastAPI()

@app.get("/items/{item_id}")
async def get_item(
    item_id: int = Path(
        ...,
        title="Item ID",
        ge=1,            # greater than or equal to 1
        le=10000,        # less than or equal to 10000
        description="The numeric ID of the item",
    ),
):
    return {"item_id": item_id}

The ... (Ellipsis) means “required, no default.” Available validators:

  • ge, gt, le, lt — numeric bounds
  • min_length, max_length — string bounds
  • pattern — regex constraint
@app.get("/products/{sku}")
async def get_product(sku: str = Path(..., pattern=r"^SKU-\d{6}$")):
    return {"sku": sku}

If someone hits /products/banana they get a 422. Only SKU-123456 style values pass through.

Predefined values with Enum

If we want a path param to only accept a fixed set of strings, use a str Enum. FastAPI shows a dropdown in /docs:

from enum import Enum

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

@app.get("/orders/status/{status}")
async def list_by_status(status: OrderStatus):
    if status is OrderStatus.shipped:
        return {"message": "Showing shipped orders"}
    return {"message": f"Showing {status.value} orders"}

Important: the Enum must inherit from str (or int) so OpenAPI knows the underlying primitive. Try any value outside the enum and we get 422.

Where do params come from?
URL: GET /orders/42/items?limit=10
Path param: order_id = 42  (from {order_id} in route)
Query param: limit = 10  (after the ?)

Path containing slashes

By default str path params stop at the next /. If we genuinely need to match a path like /files/foo/bar/baz.txt, we tell FastAPI with :path:

@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
    return {"file_path": file_path}

Now file_path captures everything after /files/, slashes included. Useful for proxying file paths or wiki-style URLs.

Multiple path params

Just add more {} and matching args:

@app.get("/users/{user_id}/orders/{order_id}")
async def get_user_order(user_id: int, order_id: int):
    return {"user_id": user_id, "order_id": order_id}

Order in the function signature doesn’t matter — FastAPI matches by name.

Common interview gotcha

If asked “what if the path param doesn’t match the type?” — FastAPI returns 422 with a structured error before our handler ever runs. We don’t have to write a try/except. That’s the whole pitch: validation happens at the framework boundary.