vxcontrol/pentagi · error
token is invalid
Error message
token is invalid
What it means
ValidateAPIToken failed to verify the bearer token's JWT signature, claims, or expiry using the server's global salt, so the middleware returns authResultFail with this generic invalid-token error. The token is syntactically present but cryptographically or structurally unacceptable.
Source
Thrown at backend/pkg/server/auth/auth_middleware.go:218
}
if !strings.HasPrefix(authHeader, "Bearer ") {
return authResultSkip, errors.New("bearer scheme must be used")
}
token := authHeader[7:]
if token == "" {
return authResultSkip, errors.New("token can't be empty")
}
// skip validation if using default salt (for backward compatibility)
if p.globalSalt == "" || p.globalSalt == "salt" {
return authResultSkip, errors.New("token validation disabled with default salt")
}
// try to validate as API token first (new format with JWT signing key)
apiClaims, apiErr := ValidateAPIToken(token, p.globalSalt)
if apiErr != nil {
return authResultFail, errors.New("token is invalid")
}
// check token status and get privileges through cache
status, privileges, err := p.tokenCache.GetStatus(apiClaims.TokenID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return authResultFail, errors.New("token not found in database")
}
return authResultFail, fmt.Errorf("error checking token status: %w", err)
}
if status != models.TokenStatusActive {
return authResultFail, errors.New("token has been revoked")
}
// Verify user hash matches database
dbHash, userStatus, err := p.userCache.GetUserHash(apiClaims.UID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {View on GitHub (pinned to ea665308ba)
Solutions
- Generate a fresh API token from the settings UI and update the client.
- Check the token was copied exactly — no quotes, whitespace, or line breaks.
- If the salt was rotated, reissue all tokens; old ones are permanently invalid.
- Confirm server time is correct (NTP) if tokens appear to expire early.
Example fix
// before
const token = JSON.parse(fs.readFileSync("token.json")); // object, not string
// after
const token = fs.readFileSync("token.txt", "utf8").trim(); Defensive patterns
Strategy: retry
Validate before calling
const token = loadToken();
if (!/^[A-Za-z0-9\-_=.]+$/.test(token)) {
throw new Error("API token is malformed (check for quotes/whitespace/truncation)");
} Type guard
function looksLikeJwt(t: string): boolean {
const p = t.split(".");
return p.length === 3 && p.every((s) => s.length > 0);
} Try / catch
try {
return await call(token);
} catch (e) {
if (is401(e) && /token is invalid/i.test(e.message)) {
const fresh = await mintNewToken(); // salt may have rotated; old tokens are dead
return await call(fresh);
}
throw e;
} Prevention
- Rotate salt only together with a planned reissue of all API tokens.
- Copy tokens programmatically (no clipboard/quotes); trim on load.
- Sync server clocks with NTP and set token TTLs with skew margin.
When it happens
Trigger: Sending an expired API token; a token signed with a different/old salt (salt rotated after token issuance); a corrupted or truncated token string; a cookie/session JWT or token from another installation passed as an API token.
Common situations: Salt changed in config after tokens were issued; copying a token with surrounding quotes or whitespace; old tokens surviving a server migration; clock skew making an unexpired-on-client token expired-on-server.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- token not found in database
- token has been revoked
- token required
- bearer scheme must be used
- token can't be empty
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/d39702389373d61e.
Report an issue: GitHub.