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
- Create a new API token that includes the 'chat' scope (or regenerate the existing one with chat added)
- 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
- Create dedicated tokens per integration with exactly the scopes it needs
- Re-check scopes after regenerating or editing a token
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
- API token missing required scope: {required}
- API token missing required scope: {' and '.join(sorted(missi
- This endpoint requires an API token
- HTTP ' + r.status
- API token requires chat scope
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/021d01acb6f45497.
Report an issue: GitHub.