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

  1. Verify the key still exists (GET /api-keys) before issuing the update, or refresh the list after any delete
  2. Treat a 400 with this message as 'not found' and remove the key from local state
  3. Ensure the id belongs to the authenticated user — keys are user-scoped
  4. 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

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


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/5ef5e836b39dee7a. Report an issue: GitHub.