mem0ai/mem0 · error · HTTPException

Invalid token type.

Error message

Invalid token type.

What it means

Raised by the POST /auth/refresh endpoint when the supplied JWT decodes successfully but its `type` claim is not "refresh". The server issues two token kinds (access and refresh) with distinct `type` claims, and this guard ensures only refresh tokens can be exchanged for new tokens. Sending an access token (or any other validly-signed token) to /refresh hits this branch.

Source

Thrown at server/routers/auth.py:149

        raise HTTPException(status_code=401, detail="Invalid email or password.")
    if not verify_password(body.password, user.password_hash):
        raise HTTPException(status_code=401, detail="Invalid email or password.")

    user.last_login_at = datetime.now(timezone.utc)
    db.commit()

    return TokenResponse(
        access_token=create_access_token(str(user.id), user.role),
        refresh_token=create_refresh_token(str(user.id), db),
    )


@router.post("/refresh", response_model=TokenResponse)
@limiter.limit("20/minute")
def refresh(request: Request, body: RefreshRequest, db: Session = Depends(get_db)):
    payload = decode_token(body.refresh_token)
    if payload.get("type") != "refresh":
        raise HTTPException(status_code=401, detail="Invalid token type.")

    jti = payload.get("jti")
    if not jti:
        raise HTTPException(status_code=401, detail="Refresh token is no longer valid.")

    user = db.get(User, payload["sub"])
    if user is None:
        raise HTTPException(status_code=401, detail="User not found.")

    consume_refresh_jti(jti, db)

    return TokenResponse(
        access_token=create_access_token(str(user.id), user.role),
        refresh_token=create_refresh_token(str(user.id), db),
    )


@router.get("/me", response_model=UserResponse)

View on GitHub (pinned to 001c235229)

Solutions

  1. Verify the client sends the refresh_token (the one returned alongside access_token in the login/refresh TokenResponse) to POST /auth/refresh
  2. Inspect the decoded JWT payload (e.g. jwt.decode on the client, or print claims server-side) and confirm it contains "type": "refresh"
  3. If tokens were issued before a `type` claim existed, force a fresh login to obtain new-format tokens

Example fix

// before
await post("/auth/refresh", { refresh_token: authToken.accessToken });

// after
await post("/auth/refresh", { refresh_token: authToken.refreshToken });
Defensive patterns

Strategy: validation

Validate before calling

function isRefreshToken(token) {
  const payload = JSON.parse(atob(token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/")));
  return payload.type === "refresh";
}
// before calling /auth/refresh:
if (!isRefreshToken(stored.refreshToken)) throw new Error("Not a refresh token");

Type guard

function isRefreshToken(token: string): boolean {
  const payload = JSON.parse(atob(token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/")));
  return (payload as { type?: string }).type === "refresh";
}

Try / catch

catch (e) { if (e.status === 401 && e.detail === "Invalid token type.") { /* you sent the access token: switch to refresh_token or re-login */ } throw e; }

Prevention

When it happens

Trigger: Calling POST /auth/refresh with the access_token instead of the refresh_token; reusing a token minted by a different flow that shares the secret but has no `type: "refresh"` claim; hand-crafting a token payload missing the type field.

Common situations: Frontend stores both tokens and passes the wrong one to the refresh call; token parsing code reads the same localStorage/cookie key for both; a migration changed token structure and old clients send legacy tokens.

Understand the failure class

Related errors


AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15). Data as JSON: /api/errors/ba0e099ea519a322. Report an issue: GitHub.