odysseus-dev/odysseus · error · HTTPException
API token has no owner
Error message
API token has no owner
What it means
Raised as HTTP 403 by _scope_owner when an API token passes the scope check but request.state.api_token_owner is empty/None. The token is structurally valid and sufficiently scoped, yet the authentication middleware did not attach an owner identity to it — a server-side token-record integrity issue (token row missing an owner field), not a caller mistake.
Source
Thrown at routes/codex_routes.py:94
if orig_api_token is None:
try:
delattr(request.state, "api_token")
except AttributeError:
pass
else:
request.state.api_token = orig_api_token
def _scope_owner(request: Request, allowed: set[str]) -> str:
"""Return the data owner if the caller is allowed for this Codex action."""
if getattr(request.state, "api_token", False):
scopes = set(getattr(request.state, "api_token_scopes", []) or [])
if not scopes.intersection(allowed):
required = " or ".join(sorted(allowed))
raise HTTPException(403, f"API token missing required scope: {required}")
owner = getattr(request.state, "api_token_owner", None)
if not owner:
raise HTTPException(403, "API token has no owner")
return owner
return require_user(request)
def _scope_owner_all(request: Request, required: set[str]) -> str:
"""Return owner only when an API token has every required scope."""
if getattr(request.state, "api_token", False):
scopes = set(getattr(request.state, "api_token_scopes", []) or [])
missing = required - scopes
if missing:
raise HTTPException(403, f"API token missing required scope: {' and '.join(sorted(missing))}")
owner = getattr(request.state, "api_token_owner", None)
if not owner:
raise HTTPException(403, "API token has no owner")
return owner
return require_user(request)
View on GitHub (pinned to f9235ebbf1)
Solutions
- Delete and re-create the API token through the current token management endpoint so it gets a proper owner.
- Inspect the API token DB record and backfill the missing owner value if the token must be kept.
- Check the middleware that sets request.state.api_token_owner to confirm it reads the correct column after any schema migration.
Defensive patterns
Strategy: try-catch
Validate before calling
const t = await introspectToken(tokenId);
if (!t.owner) { // token record is broken
const fresh = await createToken({scopes: t.scopes});
switchToken(fresh.token);
} Type guard
function tokenHasOwner(t: {owner?: string | null}): boolean {
return typeof t.owner === 'string' && t.owner.length > 0;
} Try / catch
try { callCodex() } catch (e) { if (e.status === 403 && e.detail === 'API token has no owner') { reissueToken(); retry once } else throw } Prevention
- Always create tokens through the management endpoint so owner is bound automatically.
- After schema migrations, run a check that no api_token rows have NULL owner.
- Retire legacy tokens during upgrades instead of carrying them forward.
When it happens
Trigger: Any /api/codex/* call with a token whose DB record has a null/blank owner, e.g. a token created by an older version before owner tracking, a migration that left owner NULL, or a token minted through a path that never set the owner.
Common situations: Upgrading the app versions where api_token schema added the owner column; tokens created programmatically bypassing the normal creation endpoint; a database restore that dropped the owner association.
Related errors
- API token missing required scope: {required}
- API token owner mismatch
- API token missing required scope: {' and '.join(sorted(missi
- This endpoint requires an API token
- API token is not scoped for chat
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/cbfb800e5b75358c.
Report an issue: GitHub.