immich-app/immich · error · BadRequestException

This endpoint can only be used with a session token

Error message

This endpoint can only be used with a session token

What it means

Session.create issues child API keys/sessions but requires the caller to already be authenticated with a session token (auth.session set). Requests authenticated only by an API key have no session, so the endpoint refuses with this BadRequestException.

Solutions

  1. Authenticate with a session token (log in via web/password flow) before calling this endpoint.
  2. If automating, first perform a login to obtain a session cookie, then call the endpoint.
  3. Use the API key only for endpoints that permit key-based auth; create the session interactively.

Example fix

// before
await api.createSession(apiKeyAuth); // API key -> no session
// after
const session = await login(email, password); // yields session token
await api.createSession(sessionAuth);
Defensive patterns

Strategy: validation

Validate before calling

function canCreateSession(auth) {
  return Boolean(auth && auth.session && auth.session.id);
}
if (!canCreateSession(auth)) {
  throw new Error('Session creation requires session-token auth, not an API key');
}

Type guard

const hasSession = (auth) =>
  typeof auth === 'object' && auth !== null && 'session' in auth && auth.session != null;

Try / catch

try {
  await api.createSession(dto);
} catch (e) {
  if (e.status === 400 && /session token/.test(e.message)) {
    await loginWithPassword(); // obtain a session, then retry
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: POST /session (create) with an Authorization header carrying an API key instead of a session cookie/token — auth.session is undefined in the resolved AuthDto.

Common situations: Scripts or CI jobs automating session creation using an API key; a user logged in via API key in a client trying to create child sessions; omitting login before calling this endpoint.

Related errors


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

Appendix: source

Thrown at server/src/services/session.service.ts:32

import { BaseService } from 'src/services/base.service.js';

@Injectable()
export class SessionService extends BaseService {
  @OnJob({ name: JobName.SessionCleanup, queue: QueueName.BackgroundTask })
  async handleCleanup(): Promise<JobStatus> {
    const sessions = await this.sessionRepository.cleanup();
    for (const session of sessions) {
      this.logger.verbose(`Deleted expired session token: ${session.deviceOS}/${session.deviceType}`);
    }

    this.logger.log(`Deleted ${sessions.length} expired session tokens`);

    return JobStatus.Success;
  }

  async create(auth: AuthDto, dto: SessionCreateDto): Promise<SessionCreateResponseDto> {
    if (!auth.session) {
      throw new BadRequestException('This endpoint can only be used with a session token');
    }

    const token = this.cryptoRepository.randomBytesAsText(32);
    const hashed = this.cryptoRepository.hashSha256(token);
    const session = await this.sessionRepository.create({
      parentId: auth.session.id,
      userId: auth.user.id,
      expiresAt: dto.duration ? DateTime.now().plus({ seconds: dto.duration }).toJSDate() : null,
      deviceType: dto.deviceType,
      deviceOS: dto.deviceOS,
      token: hashed,
    });

    return { ...mapSession(session), token };
  }

  async getAll(auth: AuthDto): Promise<SessionResponseDto[]> {
    const sessions = await this.sessionRepository.getByUserId(auth.user.id);

View on GitHub (pinned to e55ac299a4)