{"record":{"id":"1e6309e6ac5e6674","repo":"ruvnet/RuView","slug":"unsupported-api-version-version","errorCode":null,"errorMessage":"Unsupported API version: {version}","messagePattern":"Unsupported API version: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"plans/phase2-architecture/api-architecture.md","lineNumber":346,"sourceCode":"    \n    def __init__(self):\n        self.versions = {\n            'v1': {\n                'status': 'stable',\n                'deprecated': False,\n                'sunset_date': None\n            },\n            'v2': {\n                'status': 'beta',\n                'deprecated': False,\n                'sunset_date': None\n            }\n        }\n    \n    def get_router(self, version: str):\n        \"\"\"Get router for specific API version\"\"\"\n        if version not in self.versions:\n            raise ValueError(f\"Unsupported API version: {version}\")\n        \n        if version == 'v1':\n            from .v1 import router as v1_router\n            return v1_router\n        elif version == 'v2':\n            from .v2 import router as v2_router\n            return v2_router\n    \n    def check_deprecation(self, version: str):\n        \"\"\"Check if API version is deprecated\"\"\"\n        version_info = self.versions.get(version)\n        \n        if version_info and version_info['deprecated']:\n            return {\n                'deprecated': True,\n                'sunset_date': version_info['sunset_date'],\n                'migration_guide': f'/docs/migration/{version}-to-v{int(version[1])+1}'\n            }","sourceCodeStart":328,"sourceCodeEnd":364,"githubUrl":"https://github.com/ruvnet/RuView/blob/4685618388a5e49fad5b3005806f3bdd6a7c25c3/plans/phase2-architecture/api-architecture.md#L328-L364","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass exactly 'v1' or 'v2' — the two registered keys","Normalize before calling: version = version.strip().lower() and strip surrounding slashes","If you need a new version, add it to the versions registry dict first, then handle it in get_router","Validate the version at the request boundary and return FastAPI 400/404 rather than letting ValueError escape"],"exampleFix":"# before\nrouter = manager.get_router(path_version)  # ValueError on 'V1' or 'v1/'\n\n# after\nversion = path_version.strip(\"/\").strip().lower()\nif version not in manager.versions:\n    raise HTTPException(status_code=404, detail=f\"Unsupported API version: {version}\")\nrouter = manager.get_router(version)","handlingStrategy":"validation","validationCode":"SUPPORTED_API_VERSIONS = {\"v1\", \"v2\"}\nversion = raw_version.strip(\"/\").strip().lower()\nif version not in SUPPORTED_API_VERSIONS:\n    raise HTTPException(status_code=404, detail=f\"Unsupported API version: {raw_version!r}\")\nrouter = manager.get_router(version)","typeGuard":"from typing import Literal\n\ndef is_supported_api_version(v: str) -> bool:\n    return v in {\"v1\", \"v2\"}\n\n# after guard, annotate for narrow typing:\ndef get_router_checked(version: str) -> Literal[\"v1\", \"v2\"]-routed: ...","tryCatchPattern":"try:\n    router = manager.get_router(version)\nexcept ValueError as e:\n    raise HTTPException(status_code=404, detail=str(e)) from e  # client-facing 404, not a 500","preventionTips":["Normalize version strings (strip slashes/whitespace, lower-case) at the request boundary","Expose the supported set from the manager (sorted(manager.versions)) in a discovery endpoint","Register new versions in the versions dict before shipping clients that use them"],"tags":["api","versioning","validation","python","fastapi","spec"],"backgroundTag":null,"analyzedSha":"4685618388a5e49fad5b3005806f3bdd6a7c25c3","analyzedAt":"2026-08-16T06:09:40.886Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}