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

  1. Delete and re-create the API token through the current token management endpoint so it gets a proper owner.
  2. Inspect the API token DB record and backfill the missing owner value if the token must be kept.
  3. 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

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


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