{"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":84,"sourceCode":"  SyncRequestType.AlbumsV2,\n  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.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":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/sync.service.ts#L66-L102","documentation":"Thrown (as ForbiddenException) by SyncService.throwSessionRequired whenever a sync endpoint is invoked by a request whose AuthDto has no session id, i.e. it was authenticated with an API key instead of a user session. The sync protocol relies on a persistent server-side session to store ack checkpoints, so API keys are explicitly rejected.","triggerScenarios":"Calling GET /sync/acks, POST /sync/acks, DELETE /sync/acks, or the /sync/stream SSE endpoint with an x-api-key header (or any auth flow that yields an API-key AuthDto) instead of a session cookie or Bearer JWT.","commonSituations":"Scripts/CLI tools reused an API key against the sync API; mobile client fell back to API-key auth after session expiry; integration tests reused the admin API key for sync fixtures.","solutions":["Authenticate sync requests with a session token (cookie or Authorization: Bearer <jwt>), not x-api-key.","Re-login the user to obtain a fresh session, then retry.","For automation that needs sync data, obtain a session token via the /auth/login endpoint first.","If you must use a long-lived credential, request a non-expiring session from the auth API instead of an API key."],"exampleFix":"// before\nfetch('/sync/stream', { headers: { 'x-api-key': KEY } });\n// after\nconst { accessToken } = await login(email, password);\nfetch('/sync/stream', { headers: { Authorization: `Bearer ${accessToken}` } });","handlingStrategy":"type-guard","validationCode":"function isSessionAuth(auth: { session?: { id?: string } | null }): boolean {\n  return Boolean(auth?.session?.id);\n}\nif (!isSessionAuth(auth)) { /* use /auth/login instead of x-api-key */ }","typeGuard":"function isSessionAuth(auth: unknown): auth is { session: { id: string } } {\n  return typeof auth === 'object' && !!auth\n    && typeof (auth as any).session?.id === 'string';\n}","tryCatchPattern":"try { await syncApi.stream(req); }\ncatch (e) {\n  if (e instanceof ForbiddenException && /cannot be used with API keys/.test(e.message)) {\n    // re-login to obtain a session token, then retry once\n  }\n}","preventionTips":["Never reuse API keys for sync endpoints; keep a separate session token in the client.","In tests, authenticate via /auth/login rather than the admin API key.","Detect 403 on sync and prompt the user to re-authenticate."],"tags":["auth","sync","api-key","session","forbidden"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}