immich-app/immich · error · BadRequestException

SyncRequestType.AssetFacesV1 is deprecated, use…

Error message

SyncRequestType.AssetFacesV1 is deprecated, use SyncRequestType.AssetFacesV2 instead

What it means

Generic deprecation guard: the AssetFacesV1 sync handler is now a stub that throws for any client still requesting SyncRequestType.AssetFacesV1, since face data moved to the AssetFacesV2 protocol. It always throws when the legacy type is selected, telling outdated clients to upgrade their sync request type; the at-fault input is the deprecated SyncRequestType.AssetFacesV1 query parameter.

Solutions

  1. Update the Immich mobile app to an AssetFacesV2-capable version
  2. Replace AssetFacesV1 with SyncRequestType.AssetFacesV2 in the sync request

Example fix

// before
requests: [{ type: SyncRequestType.AssetFacesV1 }]
// after
requests: [{ type: SyncRequestType.AssetFacesV2 }]
Defensive patterns

Strategy: validation

Validate before calling

if (requests.some(r => r.type === SyncRequestType.AssetFacesV1)) throw new Error('Use AssetFacesV2');

Type guard

null

Try / catch

try {
  await syncStream(requests);
} catch (e) {
  if (e.status === 400 && e.message.includes('AssetFacesV1')) {
    requests = requests.map(r => r.type === 'AssetFacesV1' ? { ...r, type: 'AssetFacesV2' } : r);
  }
}

Prevention

When it happens

Trigger: Client includes SyncRequestType.AssetFacesV1 in its sync request; handlers dispatches to syncAssetFacesV1 which always throws.

Common situations: Facial-recognition sync from older apps against a newer server.

Understand the failure class

Background: "is deprecated and will be removed" — deprecation warnings for old API names, keywords, and options, and how to migrate before the removal release — this error's family across 29 libraries.

Related errors


AI-assisted analysis of immich-app/immich@f48d4b3321 (2026-09-15). Data as JSON: /api/errors/a94c6744aa82f3e4. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/sync.service.ts:896

    }
  }

  private async syncPeopleV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) {
    const deleteType = SyncEntityType.PersonDeleteV1;
    const deletes = this.syncRepository.person.getDeletes({ ...options, ack: checkpointMap[deleteType] });
    for await (const { id, ...data } of deletes) {
      await send(response, { type: deleteType, ids: [id], data });
    }

    const upsertType = SyncEntityType.PersonV1;
    const upserts = this.syncRepository.person.getUpserts({ ...options, ack: checkpointMap[upsertType] });
    for await (const { updateId, ...data } of upserts) {
      await send(response, { type: upsertType, ids: [updateId], data });
    }
  }

  private syncAssetFacesV1(): Promise<void> {
    throw new BadRequestException(
      'SyncRequestType.AssetFacesV1 is deprecated, use SyncRequestType.AssetFacesV2 instead',
    );
  }

  // TODO(v5) drop when AssetFacesV2 is removed
  private async syncAssetFacesV2(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) {
    const deleteType = SyncEntityType.AssetFaceDeleteV1;
    const deletes = this.syncRepository.assetFace.getDeletesV2({ ...options, ack: checkpointMap[deleteType] });
    for await (const { id, ...data } of deletes) {
      await send(response, { type: deleteType, ids: [id], data });
    }

    const upsertType = SyncEntityType.AssetFaceV2;
    const upserts = this.syncRepository.assetFace.getUpsertsV2({ ...options, ack: checkpointMap[upsertType] });
    for await (const { updateId, ...data } of upserts) {
      await send(response, { type: upsertType, ids: [updateId], data });
    }
  }

View on GitHub (pinned to f48d4b3321)