{"record":{"id":"d63c8a88ceabe569","repo":"immich-app/immich","slug":"sync-endpoints-cannot-be-used-with-api-keys","errorCode":null,"errorMessage":"Sync endpoints cannot be used with API keys","messagePattern":"Sync endpoints cannot be used with API keys","errorType":"exception","errorClass":"ForbiddenException","httpStatus":403,"severity":"error","filePath":"server/src/services/sync.service.ts","lineNumber":97,"sourceCode":"  SyncRequestType.AlbumUsersV1,\n  SyncRequestType.AlbumToAssetsV1,\n  SyncRequestType.AssetExifsV1,\n  SyncRequestType.AlbumAssetExifsV1,\n  SyncRequestType.AssetOcrV1,\n  SyncRequestType.PartnerAssetExifsV1,\n  SyncRequestType.MemoriesV1,\n  SyncRequestType.MemoryToAssetsV1,\n  SyncRequestType.PeopleV1,\n  SyncRequestType.AssetFacesV1,\n  SyncRequestType.AssetFacesV2,\n  SyncRequestType.AssetFacesV3,\n  SyncRequestType.UserMetadataV1,\n  SyncRequestType.AssetMetadataV1,\n  SyncRequestType.AssetEditsV1,\n];\n\nconst throwSessionRequired = () => {\n  throw new ForbiddenException('Sync endpoints cannot be used with API keys');\n};\n\n@Injectable()\nexport class SyncService extends BaseService {\n  getAcks(auth: AuthDto) {\n    const sessionId = auth.session?.id;\n    if (!sessionId) {\n      return throwSessionRequired();\n    }\n\n    return this.syncCheckpointRepository.getAll(sessionId);\n  }\n\n  async setAcks(auth: AuthDto, dto: SyncAckSetDto) {\n    const sessionId = auth.session?.id;\n    if (!sessionId) {\n      return throwSessionRequired();\n    }","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/immich-app/immich/blob/f48d4b332127ad365ba256108799ca8f571d2dd5/server/src/services/sync.service.ts#L79-L115","documentation":"The sync endpoints require an authenticated session (created via password login), not a machine-learning/API-key auth. throwSessionRequired raises ForbiddenException when the AuthDto has no session attached, because sync checkpoints/acks are scoped to a session.","triggerScenarios":"Calling any sync endpoint (getAcks, setAcks, deleteAcks, streamInternal) with an AuthDto whose auth.session is undefined — i.e. authenticated via API key or another non-session auth type.","commonSituations":"Scripts using an API key to call /api/sync endpoints; mobile-client-only sync API invoked from automation; using API keys generated for external tools.","solutions":["Authenticate with username/password login to obtain a session instead of an API key","Use the Immich mobile app or a session-based client for sync operations","If scripting is required, log in via POST /api/auth/login and use the returned session token"],"exampleFix":"// before\nheaders: { 'x-api-key': apiKey }\ncall GET /api/sync/acks\n// after\nconst { accessToken } = await login(email, password)\nheaders: { Authorization: 'Bearer ' + accessToken }\ncall GET /api/sync/acks","handlingStrategy":"validation","validationCode":"const isSyncCapable = (auth) => Boolean(auth.session?.id);\nif (!isSyncCapable(auth)) throw new Error('Sync requires a login session, not an API key');","typeGuard":"function hasSession(auth) { return typeof auth === 'object' && auth !== null && 'session' in auth && auth.session != null && typeof auth.session.id === 'string'; }","tryCatchPattern":"try {\n  await syncClient.getAcks();\n} catch (e) {\n  if (e.status === 403) { await loginWithPassword(); /* retry with session */ }\n}","preventionTips":["Use API keys only for non-sync endpoints","Create sessions via /api/auth/login for sync workloads","Document that sync APIs are mobile/session-only"],"tags":["auth","sync","api-key","forbidden"],"backgroundTag":"authentication-required","analyzedSha":"f48d4b332127ad365ba256108799ca8f571d2dd5","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}