Question: Based on a few FastAPI tutorials, including this, I made a simple FastAPI app: from fastapi import FastAPI, Request app = FastAPI() # also tried FastAPI(root_path="/api/v1") @app.get("/app") def read_main(request: Request): return {"message": "Hello World", "root_path": request.scope.get("root_path")}  Which i want to have at a path other than root (e.g. /api/vi)... Again based on most tutorials and common sense, I tried to start it with e.g.: uvicorn main:app --root-path /api/v1  The service comes up ok (on http://127.0.0.1:8000), however, the root-path seems to be ignored: Any GET request to http://127.0.0.1:8000/ gives: message "Hello World" root_path "/api/v1"  and any GET request to http://127.0.0.1:8000/api/v1 gives: detail "Not Found"  I would expect the requests to produce the reverse outcomes... What is going on here?!? I also tried initializing FastAPI with FastAPI(root_path="/api/v1") as well as switching to hypercorn without avail... Details of the versions of apps (I might have tried a few others as well, though these should be the latest tried): python 3.9.7 hf930737_3_cpython conda-forge fastapi 0.85.1 pyhd8ed1ab_0 conda-forge uvicorn 0.20.0 py39h06a4308_0 hypercorn 0.14.3 py39hf3d152e_1 conda-forge 

Answer: As noted by @MatsLindh in the comments section, root_path (or --root-path) does not change your application's prefix path, but is rather designated for behind the proxy cases, where "you might need to use a proxy server like Traefik or Nginx with a configuration that adds an extra path prefix that is not seen by your application" (see the relevant documentation). As described in the documentation:  Proxy with a stripped path prefix Having a proxy with a stripped path prefix, in this case, means that you could declare a path at /app in your code, but then, you add a layer on top (the proxy) that would put your FastAPI application under a path like /api/v1. In this case, the original path /app would actually be served at /api/v1/app. Even though all your code is written assuming there's just /app. And the proxy would be "stripping" the path prefix on the fly before transmitting the request to Uvicorn, keep your application convinced that it is serving at /app, so that you don't have to update all your code to include the prefix /api/v1. Up to here, everything would work as normally. But then, when you open the integrated docs UI (the frontend), it would expect to get the OpenAPI schema at /openapi.json, instead of /api/v1/openapi.json. So, the frontend (that runs in the browser) would try to reach /openapi.json and wouldn't be able to get the OpenAPI schema (it would show "Failed to load API definition" error). Because we have a proxy with a path prefix of /api/v1 for our app, the frontend needs to fetch the OpenAPI schema at /api/v1/openapi.json. The docs UI would also need the OpenAPI schema to declare that this API server is located at /api/v1 (behind the proxy). To achieve this, you can use the command line option --root-path:  uvicorn main:app --root-path /api/v1  [...] Alternatively, if you don't have a way to provide a command line option like --root-path or equivalent, you can set the root_path parameter when creating your FastAPI app: app = FastAPI(root_path="/api/v1")   Option 1 Hence, in your case, since you are not using a proxy, but rather need to have a custom prefix for your API, you could instead use an APIRouter, which allows you to define a prefix for the API routes (note that the prefix must not include a final /). You can either give the prefix when instantiating the APIRouter (e.g., router = APIRouter(prefix='/api/v1')) or using .include_router(), which, as described in the documentation, would allow you to include the same router multiple times with different prefix:  You can also use .include_router() multiple times with the same router using different prefixes. This could be useful, for example, to expose the same API under different prefixes, e.g. /api/v1 and /api/latest. This is an advanced usage that you might not really need, but it's there in case you do.  The /app endpoint in the example below can be accessed at http://127.0.0.1:8000/api/v1/app. Working Example from fastapi import FastAPI from fastapi.routing import APIRouter router = APIRouter() @router.get('/app') def main(): return 'Hello world!' app = FastAPI() app.include_router(router, prefix='/api/v1')  Once you have multiple versions of the API endpoints, you could use: from fastapi import FastAPI from fastapi.routing import APIRouter router_v1 = APIRouter() router_v2 = APIRouter() @router_v1.get('/app') def main(): return 'Hello world - v1' @router_v2.get('/app') def main(): return 'Hello world - v2' app = FastAPI() app.include_router(router_v1, prefix='/api/v1') app.include_router(router_v2, prefix='/api/v2') app.include_router(router_v2, prefix='/latest') # optional  Option 2 Alternatively, one could also mount sub-application(s) with the desired prefix, as demonstrated in this answer and this answer (see Option 3). As described in the documentation:  When you mount a sub-application as described above, FastAPI will take care of communicating the mount path for the sub-application using a mechanism from the ASGI specification called a root_path. That way, the sub-application will know to use that path prefix for the docs UI. And the sub-application could also have its own mounted sub-applications and everything would work correctly, because FastAPI handles all these root_paths automatically.  Hence, in the example given below, you can access the /app endpoint from the main app at http://127.0.0.1:8000/app and the /app endpoint from the sub app at http://127.0.0.1:8000/api/v1/app. Similarly, the Swagger UI autodocs can be accessed at http://127.0.0.1:8000/docs and http://127.0.0.1:8000/api/v1/docs, respectively. Working Example from fastapi import FastAPI app = FastAPI() subapi = FastAPI() @app.get('/app') def read_main(): return {'message': 'Hello World from main app'} @subapi.get('/app') def read_sub(): return {'message': 'Hello World from sub API'} app.mount('/api/v1', subapi)