tiangolo/fastapi · error · FastAPIError

Prefix and path cannot be both empty

Error message

Prefix and path cannot be both empty (path operation: {name})

What it means

Raised in `APIRouter.include_router` (fastapi/routing.py:3293) as `FastAPIError` when `prefix` is empty/falsy AND one of the included router's routes has an empty `path`. FastAPI forbids a route with both empty prefix and empty path because it would be ambiguous (it would match everything). The offending path operation's name is included so you can locate it.

Solutions

  1. Add a non-empty prefix on include: `app.include_router(sub, prefix='/sub')`.
  2. Fix the empty route path to use '/': `@sub.get('/')` instead of `@sub.get('')`.
  3. Ensure every path operation in the included router has a path starting with '/'.

Example fix

// before
@sub.get('')
async def root(): ...
app.include_router(sub)  # prefix empty, path empty
// after
@sub.get('/')
async def root(): ...
app.include_router(sub, prefix='/sub')
Defensive patterns

Strategy: validation

Validate before calling

def safe_include(parent, sub, prefix: str | None) -> None:
    prefix = prefix or ''
    if not prefix:
        for r in getattr(sub, 'routes', []):
            p = getattr(r, 'path', None)
            if p is not None and not p:
                raise ValueError(f'sub-router has an empty path; add a prefix or fix {getattr(r, "name", "?")}')
    parent.include_router(sub, prefix=prefix)

Type guard

def include_is_safe(sub, prefix: object) -> bool:
    if isinstance(prefix, str) and prefix:
        return True
    return all(getattr(r, 'path', '/') for r in getattr(sub, 'routes', []))

Prevention

When it happens

Trigger: Including a sub-router with no prefix (`include_router(sub)` or `include_router(sub, prefix='')`) when `sub` contains a route declared with `@sub.get('')` or `@sub.route('')`. Also occurs when a sub-router itself was created with an empty path and is included without a prefix.

Common situations: Refactoring where a prefix was removed from `include_router` but the route paths were not updated to start with '/'. Declaring `@router.get('')` expecting it to mean '/'. Copying routers between apps and dropping the prefix argument.

Related errors


AI-assisted analysis of tiangolo/fastapi@3e8d1526d8 (2026-08-11). Data as JSON: /api/errors/9ed5c86d5157d5e2. Report an issue: GitHub.

Appendix: source

Thrown at fastapi/routing.py:3293

        )
        if prefix:
            assert prefix.startswith("/"), "A path prefix must start with '/'"
            assert not prefix.endswith("/"), (
                "A path prefix must not end with '/', as the routes will start with '/'"
            )
        else:
            for route, route_context in _iter_routes_with_context(router.routes):
                if route_context is None:
                    path = getattr(route, "path", None)
                    name = getattr(route, "name", "unknown")
                elif route_context.starlette_route is not None:
                    path = getattr(route_context.starlette_route, "path", None)
                    name = getattr(route_context.starlette_route, "name", "unknown")
                else:
                    path = route_context.path
                    name = route_context.name
                if path is not None and not path:
                    raise FastAPIError(
                        f"Prefix and path cannot be both empty (path operation: {name})"
                    )
        include_context = _RouterIncludeContext.for_include(
            parent_router=self,
            included_router=router,
            prefix=prefix,
            tags=tags,
            dependencies=dependencies,
            default_response_class=default_response_class,
            responses=responses,
            callbacks=callbacks,
            deprecated=deprecated,
            include_in_schema=include_in_schema,
            generate_unique_id_function=generate_unique_id_function,
        )
        self.routes.append(
            _IncludedRouter(original_router=router, include_context=include_context)
        )

View on GitHub (pinned to 3e8d1526d8)