immich-app/immich · error · UnauthorizedException
Invalid API key
Error message
Invalid API key
What it means
API-key authentication hashes the presented key and compares it (via validateSecret) against the stored hash. If no API key record matches the provided secret, an UnauthorizedException('Invalid API key') is thrown.
Solutions
- Regenerate or re-copy the API key from user settings and update the client
- Ensure the header value is the secret key with no surrounding whitespace or quotes
- Confirm the key is still active and was not deleted/rotated
- Verify the client sends the key on the expected header for this endpoint
Example fix
// before
const key = process.env.IMMICH_API_KEY.trim().split(' ')[0];
// after
const key = process.env.IMMICH_API_KEY; Defensive patterns
Strategy: validation
Validate before calling
if (!/^[A-Za-z0-9]{20,}$/.test(apiKey)) throw new Error('malformed api key'); Try / catch
catch (e) { if (e.status === 401 && e.message === 'Invalid API key') { /* refresh key from settings */ } } Prevention
- Store keys in env without quotes/whitespace
- Rotate keys in lockstep with clients
- Send the secret, not the key label
When it happens
Trigger: Request with x-api-key header whose value does not match any stored key hash, or validate() routed to API-key auth with an empty/malformed key.
Common situations: Key rotated/revoked server-side while client still uses the old value; key truncated or whitespace-padded in env config; copying the key name/label instead of the secret; regenerating the key without updating integrations.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of immich-app/immich@f48d4b3321 (2026-09-15).
Data as JSON: /api/errors/c67a4fcb828456a9.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/auth.service.ts:537
}
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 f48d4b3321)