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
- Authenticate with a session token (log in via web/password flow) before calling this endpoint.
- If automating, first perform a login to obtain a session cookie, then call the endpoint.
- 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
- Log in with credentials before calling session endpoints.
- Do not use API keys for interactive session flows.
- Check auth kind in client code before hitting session APIs.
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
- Invalid license key
- No fields to update
- Not in maintenance mode
- Shared link is not password protected
- This endpoint can only be used with a session token
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)