immich-app/immich · error · UnauthorizedException
Invalid API key
Error message
Invalid API key
What it means
Thrown by AuthService.validateApiKey. The supplied key is SHA-256 hashed and looked up in apiKeyRepository.getKey; the lookup must return a record with a non-null user. Because keys are stored only as hashes, this fires both for malformed keys and for valid-looking keys that are simply not registered.
Source
Thrown at server/src/services/auth.service.ts:533
}
private isValidSharedLink(
sharedLink?: AuthSharedLink & { user: AuthUser | null },
): sharedLink is AuthSharedLink & { user: AuthUser } {
return !!sharedLink?.user && (!sharedLink.expiresAt || new Date(sharedLink.expiresAt) > new Date());
}
private async validateApiKey(key: string): Promise<AuthDto> {
const hashed = this.cryptoRepository.hashSha256(key);
const apiKey = await this.apiKeyRepository.getKey(hashed);
if (apiKey?.user) {
return {
user: apiKey.user,
apiKey,
};
}
throw new UnauthorizedException('Invalid API key');
}
private validateSecret(inputSecret: string, existingHash?: string | null): boolean {
if (!existingHash) {
return false;
}
return this.cryptoRepository.compareBcrypt(inputSecret, existingHash);
}
private async validateSession(token: string, headers: IncomingHttpHeaders): Promise<AuthDto> {
const hashed = this.cryptoRepository.hashSha256(token);
const session = await this.sessionRepository.getByToken(hashed);
if (session?.user) {
const { appVersion, deviceOS, deviceType } = getUserAgentDetails(headers);
const now = DateTime.now();
const updatedAt = DateTime.fromJSDate(session.updatedAt);
const diff = now.diff(updatedAt, ['hours']);View on GitHub (pinned to 199723261c)
Solutions
- Regenerate the API key from the user's API Keys settings and copy it without trailing whitespace.
- Confirm the key belongs to this server instance/database (api_keys table is present and populated).
- Send the key in the header the guard expects (commonly x-api-key), not as a bearer token.
- If integrating programmatically, validate key presence and non-empty length before the request and fail fast client-side.
Example fix
// before
const res = await fetch(url, { headers: { 'x-api-key': keyFromConfig } });
// after
if (!keyFromConfig || keyFromConfig.trim().length < 16) {
throw new Error('API key is missing or malformed');
}
const res = await fetch(url, { headers: { 'x-api-key': keyFromConfig.trim() } });
if (res.status === 401) throw new Error('API key rejected by server (revoked or wrong instance)'); Defensive patterns
Strategy: validation
Validate before calling
function isValidApiKeyFormat(key: unknown): boolean {
return typeof key === 'string' && key.trim().length >= 16 && /^[A-Za-z0-9_-]+$/.test(key);
}
if (!isValidApiKeyFormat(process.env.IMMICH_API_KEY)) {
throw new Error('IMMICH_API_KEY is missing or malformed');
} Type guard
function isApiKey(v: unknown): v is string {
return typeof v === 'string' && v.trim().length >= 16;
} Prevention
- Store API keys in a secrets manager, never hard-coded, and copy them without whitespace.
- Issue one key per integration so revocation does not affect others.
- Tie API keys to the specific instance/database that issued them.
When it happens
Trigger: Any request carrying an x-api-key header (or equivalent) whose value does not hash to a known, user-backed API key record: revoked keys, mistyped keys, keys from a different environment/database, or an empty header.
Common situations: Using a key issued against a different Immich instance or after a DB restore that lost the api_keys table; sending the key in the wrong header; whitespace/newline copied into the key; key was deleted by the owner.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Password login has been disabled
- Incorrect email or password
- Unauthorized
- Missing required permission: ${requestedPermission}
- Authentication required
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/c67a4fcb828456a9.
Report an issue: GitHub.