odysseus-dev/odysseus · error · HTTPException

API token is not scoped for chat

Error message

API token is not scoped for chat

What it means

HTTP 403 from POST /v1/chat: a valid API token was presented but its scope set does not include 'chat'. Scopes are attached by the token middleware onto request.state.api_token_scopes; tokens are minted with a restricted scope list and this endpoint requires the chat scope.

Source

Thrown at routes/webhook/webhook_routes.py:243

                if model_lower.startswith(prefix):
                    return KNOWN_PROVIDERS[prov]
        return None

    class SyncChatRequest(BaseModel):
        message: str = Field(..., max_length=MAX_MESSAGE_LEN)
        model: Optional[str] = Field(None, max_length=200)
        session: Optional[str] = Field(None, max_length=100)
        api_key: Optional[str] = Field(None, max_length=256)
        base_url: Optional[str] = Field(None, max_length=MAX_URL_LEN)
        provider: Optional[str] = Field(None, max_length=50)

    @router.post("/v1/chat")
    async def sync_chat(request: Request, body: SyncChatRequest):
        if not getattr(request.state, "api_token", False):
            raise HTTPException(403, "This endpoint requires an API token")
        scopes = set(getattr(request.state, "api_token_scopes", []) or [])
        if "chat" not in scopes:
            raise HTTPException(403, "API token is not scoped for chat")
        token_owner = getattr(request.state, "api_token_owner", None)

        from core.models import ChatMessage
        from src.llm_core import llm_call_async
        from src.endpoint_resolver import build_chat_url, build_headers, build_models_url, normalize_base

        message = body.message.strip()
        if not message:
            raise HTTPException(400, "Message is required")

        session_id = body.session
        sess = None

        # --- Case 1: Resume an existing session ---
        if session_id and session_manager:
            try:
                sess = session_manager.get_session(session_id)
            except (KeyError, Exception):

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Create a new API token that includes the 'chat' scope (or regenerate the existing one with chat added)
  2. Verify the token's scopes via the admin token endpoint before wiring it into automation
Defensive patterns

Strategy: validation

Validate before calling

# at setup time: verify the token carries the chat scope
me = requests.get(f"{base}/api/keys/me", headers=h).json()
assert "chat" in me.get("scopes", []), "token lacks chat scope"

Try / catch

if resp.status_code == 403 and 'scoped for chat' in detail:
    regenerate_token_with_scopes(["chat"])  # then update the stored credential

Prevention

When it happens

Trigger: Calling /v1/chat with a token created for webhook-delivery-only or admin scopes; using a token whose scopes string was edited at creation time to omit 'chat'.

Common situations: Reusing a narrowly-scoped automation token for a new chat integration; creating a token before the chat feature existed and not regenerating it.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/021d01acb6f45497. Report an issue: GitHub.