immich-app/immich · error · BadRequestException
API Key not found
Error message
API Key not found
What it means
Thrown by ApiKeyService.update when apiKeyRepository.getById(userId, id) returns null for the authenticated user. The lookup is scoped to auth.user.id, so a missing row means the id does not exist, belongs to a different user, or was already deleted. Returned as BadRequestException (HTTP 400) rather than NotFound, so callers must not assume 404 semantics.
Source
Thrown at server/src/services/api-key.service.ts:32
if (auth.apiKey && !isGranted({ requested: dto.permissions, current: auth.apiKey.permissions })) {
throw new BadRequestException('Cannot grant permissions you do not have');
}
const entity = await this.apiKeyRepository.create({
key: hashed,
name: dto.name || 'API Key',
userId: auth.user.id,
permissions: dto.permissions,
});
return { secret: token, apiKey: this.map(entity) };
}
async update(auth: AuthDto, id: string, dto: ApiKeyUpdateDto): Promise<ApiKeyResponseDto> {
const exists = await this.apiKeyRepository.getById(auth.user.id, id);
if (!exists) {
throw new BadRequestException('API Key not found');
}
if (
auth.apiKey &&
dto.permissions &&
!isGranted({ requested: dto.permissions, current: auth.apiKey.permissions })
) {
throw new BadRequestException('Cannot grant permissions you do not have');
}
const key = await this.apiKeyRepository.update(auth.user.id, id, { name: dto.name, permissions: dto.permissions });
return this.map(key);
}
async delete(auth: AuthDto, id: string): Promise<void> {
const exists = await this.apiKeyRepository.getById(auth.user.id, id);
if (!exists) {View on GitHub (pinned to 199723261c)
Solutions
- Verify the key still exists (GET /api-keys) before issuing the update, or refresh the list after any delete
- Treat a 400 with this message as 'not found' and remove the key from local state
- Ensure the id belongs to the authenticated user — keys are user-scoped
- Guard against double-submit / race with a UI lock after the first action
Example fix
// before
await sdk.updateApiKey(maybeStaleId, { name: 'renamed' });
// after
const keys = await sdk.getAllApiKeys();
if (!keys.some(k => k.id === maybeStaleId)) { /* drop from UI */ return; }
await sdk.updateApiKey(maybeStaleId, { name: 'renamed' }); Defensive patterns
Strategy: validation
Validate before calling
// Confirm the key still exists for the user before updating
const keys = await sdk.getAllApiKeys();
if (!keys.some(k => k.id === id)) {
throw new Error(`API key ${id} not found for this user`);
}
await sdk.updateApiKey(id, dto); Type guard
function isApiKeyRow(x: unknown): x is { id: string; name: string; permissions: string[] } {
return typeof x === 'object' && x !== null && typeof (x as any).id === 'string';
} Try / catch
try {
await sdk.updateApiKey(id, dto);
} catch (e) {
if (e instanceof HttpError && e.status === 400 && /API Key not found/.test(e.message)) {
ui.removeKey(id); // treat as already-deleted
return;
}
throw e;
} Prevention
- Refresh the key list after any create/delete before issuing further edits
- Make update handlers idempotent against 'not found' outcomes
- Keys are user-scoped — never reuse ids observed in another account
When it happens
Trigger: PUT /api-keys/:id where :id is not a valid API key id for the authenticated user; calling update twice where the second call follows a successful delete; passing a UUID from a different user's account.
Common situations: Stale client state after a key was deleted in another session/tab; copy-paste of the wrong id; frontend cache that retains a key after removal; concurrent admin actions.
Related errors
- Asset not found
- Cannot grant permissions you do not have
- Not authenticated with an API Key
- Asset media not found
- Asset not found or asset is not a video
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/5ef5e836b39dee7a.
Report an issue: GitHub.