odysseus-dev/odysseus · error · HTTPException
API token requires chat scope
Error message
API token requires chat scope
What it means
HTTP 403 raised by require_models_scope for companion model-inventory routes. When the request authenticates with a bearer API token (request.state.api_token truthy), the token's scopes are checked and the 'chat' scope (companion.pairing.COMPANION_SCOPE) must be present. Session-authenticated requests skip the check entirely.
Source
Thrown at companion/routes.py:65
A caller sees a row when it is their own, or when it is a legacy null-owner
("shared") row. A caller must NEVER see another owner's row. Mirrors the
`owner_filter` rule used elsewhere, expressed as a pure predicate so it can
be tested directly and used as a defensive in-Python check alongside the
SQL filter.
"""
return row_owner is None or row_owner == owner
def require_models_scope(request: Request) -> None:
"""Require the companion chat scope for bearer-token model inventory."""
if not getattr(request.state, "api_token", False):
return
scopes = getattr(request.state, "api_token_scopes", None) or []
if isinstance(scopes, str):
scopes = [scope.strip() for scope in scopes.split(",")]
scope_set = {str(scope).strip() for scope in scopes if str(scope).strip()}
if _pairing.COMPANION_SCOPE not in scope_set:
raise HTTPException(403, "API token requires chat scope")
def mint_pairing_token(owner: str, invalidate=None) -> tuple[str, str]:
"""Mint a pairing token AND invalidate the auth middleware's in-memory token
cache, so the new token is accepted on the very next request without a server
restart. Returns (token_id, raw_token); the raw token is shown once.
`invalidate` is the app's request.app.state.invalidate_token_cache callable
(passed in so this stays a pure, testable unit).
"""
token_id, raw_token = _pairing.mint_token(owner)
if callable(invalidate):
invalidate()
return token_id, raw_token
def setup_companion_routes() -> APIRouter:
router = APIRouter(prefix="/api/companion", tags=["companion"])View on GitHub (pinned to f9235ebbf1)
Solutions
- Re-mint the pairing token including the chat scope (mint_pairing_token mints the companion scope)
- Or call the endpoint with the browser session (cookie auth) instead of the bearer token
- Verify the stored scopes string for the token contains 'chat' (comma/space separated entries are parsed)
Example fix
# before
curl -H 'Authorization: Bearer <token-without-chat-scope>' http://host/companion/models
# after — mint a token with the chat scope, then use it
token_id, raw = mint_pairing_token(owner, invalidate=app.state.invalidate_token_cache)
curl -H f'Authorization: Bearer {raw}' http://host/companion/models Defensive patterns
Strategy: validation
Validate before calling
scopes = (token_scopes or '').split(',') if isinstance(token_scopes, str) else (token_scopes or [])
if 'chat' not in {s.strip() for s in scopes if s.strip()}:
raise PermissionError('Re-mint token with chat scope') Try / catch
try:
require_models_scope(request)
except HTTPException as e:
if e.status_code == 403:
return JSONResponse({'error': 'token lacks chat scope'}, status_code=403)
raise Prevention
- Mint pairing tokens via mint_pairing_token so scopes are correct
- Store the scope list with the token and display it in token management UI
- Prefer session auth for browser flows to avoid scope issues
When it happens
Trigger: Calling a companion route guarded by require_models_scope with a pairing/API token that was minted without the 'chat' scope, or whose scope string is malformed so 'chat' is not in the parsed set.
Common situations: Using an old token minted before scopes were introduced; minting a token with only other scopes; passing a custom Authorization header where a session cookie would have sufficed.
Related errors
- API token missing required scope: {required}
- API token missing required scope: {' and '.join(sorted(missi
- API token owner mismatch
- API token is not scoped for chat
- Failed to update task
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/a3cb0afbeefbccbb.
Report an issue: GitHub.