{"record":{"id":"f3632765fd145228","repo":"immich-app/immich","slug":"this-endpoint-can-only-be-used-with-a-session-toke-f36327","errorCode":null,"errorMessage":"This endpoint can only be used with a session token","messagePattern":"This endpoint can only be used with a session token","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/session.service.ts","lineNumber":32,"sourceCode":"import { BaseService } from 'src/services/base.service';\n\n@Injectable()\nexport class SessionService extends BaseService {\n  @OnJob({ name: JobName.SessionCleanup, queue: QueueName.BackgroundTask })\n  async handleCleanup(): Promise<JobStatus> {\n    const sessions = await this.sessionRepository.cleanup();\n    for (const session of sessions) {\n      this.logger.verbose(`Deleted expired session token: ${session.deviceOS}/${session.deviceType}`);\n    }\n\n    this.logger.log(`Deleted ${sessions.length} expired session tokens`);\n\n    return JobStatus.Success;\n  }\n\n  async create(auth: AuthDto, dto: SessionCreateDto): Promise<SessionCreateResponseDto> {\n    if (!auth.session) {\n      throw new BadRequestException('This endpoint can only be used with a session token');\n    }\n\n    const token = this.cryptoRepository.randomBytesAsText(32);\n    const hashed = this.cryptoRepository.hashSha256(token);\n    const session = await this.sessionRepository.create({\n      parentId: auth.session.id,\n      userId: auth.user.id,\n      expiresAt: dto.duration ? DateTime.now().plus({ seconds: dto.duration }).toJSDate() : null,\n      deviceType: dto.deviceType,\n      deviceOS: dto.deviceOS,\n      token: hashed,\n    });\n\n    return { ...mapSession(session), token };\n  }\n\n  async getAll(auth: AuthDto): Promise<SessionResponseDto[]> {\n    const sessions = await this.sessionRepository.getByUserId(auth.user.id);","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/session.service.ts#L14-L50","documentation":"SessionService.create() mints a new (child) session token derived from the caller's existing session. It requires auth.session to be populated, which only happens when the request was authenticated with a session JWT — not an API key. A BadRequestException (HTTP 400) is thrown at session.service.ts:32 when auth.session is absent.","triggerScenarios":"POST /sessions (session creation) authenticated with an API key (auth.session is null for API-key requests) or with no/invalid session token. The endpoint cannot derive a parentId without an existing session.","commonSituations":"Scripts or integrations using API keys to programmatically create sessions, calling the endpoint with a bearer token that expired or was malformed, or migrating an integration that assumed API-key auth covers session creation.","solutions":["Authenticate the request with a valid session JWT (cookie or Authorization: Bearer) instead of an API key.","If programmatic long-lived access is needed, use API keys directly rather than creating child sessions.","Confirm the session token hasn't expired by calling GET /sessions before creating a child.","Regenerate/refresh the session token if it is malformed or stale."],"exampleFix":"// before - API key cannot create a session\nfetch('/sessions', { headers: { 'x-api-key': apiKey } });\n// after - session token required\nfetch('/sessions', { headers: { Authorization: `Bearer ${sessionToken}` } });","handlingStrategy":"validation","validationCode":"// Only create a child session when authenticated with a session token.\nif (!hasSessionToken(authContext)) {\n  throw new Error('Use a session token, not an API key, to create a session.');\n}\nawait sessionApi.create(authContext, dto);\n\nfunction hasSessionToken(auth: AuthDto): boolean {\n  return !!auth?.session;\n}","typeGuard":"const hasSession = (a: AuthDto | undefined): a is AuthDto & { session: NonNullable<AuthDto['session']> } =>\n  !!a && !!a.session;","tryCatchPattern":"try {\n  await sessionApi.create(auth, dto);\n} catch (e) {\n  if (e instanceof BadRequestException && /session token/i.test(e.message)) {\n    redirectToLogin();\n  } else throw e;\n}","preventionTips":["Do not use API keys for session-creation endpoints.","Detect API-key vs session auth on the client and route accordingly.","Refresh the session token before it expires."],"tags":["session","auth","api-key","validation"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}