ruvnet/RuView · warning · ValueError

Unsupported API version: {version}

Error message

Unsupported API version: {version}

What it means

Spec code in plans/phase2-architecture/api-architecture.md: APIVersionManager.get_router(version) raises ValueError for any version not in its registry dict ({'v1': stable, 'v2': beta}). There is no input normalization, so 'V1', 'v1/', ' v1', 'v3', or '' all fail identically. This is the gate that maps an API-version string to the corresponding router module.

Source

Thrown at plans/phase2-architecture/api-architecture.md:346

    
    def __init__(self):
        self.versions = {
            'v1': {
                'status': 'stable',
                'deprecated': False,
                'sunset_date': None
            },
            'v2': {
                'status': 'beta',
                'deprecated': False,
                'sunset_date': None
            }
        }
    
    def get_router(self, version: str):
        """Get router for specific API version"""
        if version not in self.versions:
            raise ValueError(f"Unsupported API version: {version}")
        
        if version == 'v1':
            from .v1 import router as v1_router
            return v1_router
        elif version == 'v2':
            from .v2 import router as v2_router
            return v2_router
    
    def check_deprecation(self, version: str):
        """Check if API version is deprecated"""
        version_info = self.versions.get(version)
        
        if version_info and version_info['deprecated']:
            return {
                'deprecated': True,
                'sunset_date': version_info['sunset_date'],
                'migration_guide': f'/docs/migration/{version}-to-v{int(version[1])+1}'
            }

View on GitHub (pinned to 4685618388)

Solutions

  1. Pass exactly 'v1' or 'v2' — the two registered keys
  2. Normalize before calling: version = version.strip().lower() and strip surrounding slashes
  3. If you need a new version, add it to the versions registry dict first, then handle it in get_router
  4. Validate the version at the request boundary and return FastAPI 400/404 rather than letting ValueError escape

Example fix

# before
router = manager.get_router(path_version)  # ValueError on 'V1' or 'v1/'

# after
version = path_version.strip("/").strip().lower()
if version not in manager.versions:
    raise HTTPException(status_code=404, detail=f"Unsupported API version: {version}")
router = manager.get_router(version)
Defensive patterns

Strategy: validation

Validate before calling

SUPPORTED_API_VERSIONS = {"v1", "v2"}
version = raw_version.strip("/").strip().lower()
if version not in SUPPORTED_API_VERSIONS:
    raise HTTPException(status_code=404, detail=f"Unsupported API version: {raw_version!r}")
router = manager.get_router(version)

Type guard

from typing import Literal

def is_supported_api_version(v: str) -> bool:
    return v in {"v1", "v2"}

# after guard, annotate for narrow typing:
def get_router_checked(version: str) -> Literal["v1", "v2"]-routed: ...

Try / catch

try:
    router = manager.get_router(version)
except ValueError as e:
    raise HTTPException(status_code=404, detail=str(e)) from e  # client-facing 404, not a 500

Prevention

When it happens

Trigger: Calling get_router() with a version taken verbatim from a request path/query (e.g. /api/V1/... or /api/v1//pose), a config value with stray whitespace or different casing, or a genuinely new version ('v3') not yet registered.

Common situations: Clients sending uppercase or padded version strings; URL routing that keeps a trailing slash in the version capture; adding a new API version without extending the versions dict; typos in configuration files.

Related errors


AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16). Data as JSON: /api/errors/1e6309e6ac5e6674. Report an issue: GitHub.