immich-app/immich · error · BadRequestException
User does not have a PIN code
Error message
User does not have a PIN code
What it means
validatePinCode is the shared gate for resetPinCode, changePinCode, and unlockSession. It first requires that the user actually has a PIN configured; if the stored pinCode is null, all PIN operations are invalid and it throws a 400 'User does not have a PIN code', since there is nothing to verify against.
Solutions
- Call the PIN setup endpoint first (setupPinCode) to create a PIN, then retry the operation.
- Check PIN status via GET /api/auth/pin-code/status before invoking any PIN endpoint.
- Use the password-based flow (e.g. password login) instead of PIN unlock when no PIN exists.
Example fix
// before
await api.authenticationApi.unlockSession({ pinCode }); // no PIN set
// after
const { hasPin } = await api.authenticationApi.getPinCodeStatus();
if (!hasPin) await api.authenticationApi.setupPinCode({ pinCode });
await api.authenticationApi.unlockSession({ pinCode }); Defensive patterns
Strategy: validation
Validate before calling
const status = await api.authenticationApi.getPinCodeStatus();
if (!status.hasPin) {
throw new Error('No PIN configured; set one up first or use password auth');
} Try / catch
try {
await api.authenticationApi.unlockSession({ pinCode });
} catch (e) {
if (e.status === 400 && e.message === 'User does not have a PIN code') {
// route user to PIN setup
}
throw e;
} Prevention
- Gate PIN-based UI behind a hasPin status check.
- Refresh PIN status after reset/change operations.
- Offer a password fallback when the PIN is not configured.
When it happens
Trigger: Calling resetPinCode, changePinCode, or unlockSession with pinCode/password credentials for a user account that has never set up a PIN (stored pinCode is null).
Common situations: Client UI showing PIN entry before the user completed PIN setup; calling unlockSession after another device reset the PIN; race where the PIN was cleared between listing and calling.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Either password or pinCode is required
- User already has a PIN code
- Asset dimensions are not available for editing
- assetIds, albumId, or userId is required
- At least two people are required for merging
AI-assisted analysis of immich-app/immich@f48d4b3321 (2026-09-15).
Data as JSON: /api/errors/5c703bc3221e70f2.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/auth.service.ts:187
await this.userRepository.update(auth.user.id, { pinCode: null });
await this.sessionRepository.lockAll(auth.user.id);
}
async changePinCode(auth: AuthDto, dto: PinCodeChangeDto) {
const user = await this.userRepository.getForPinCode(auth.user.id);
this.validatePinCode(user, dto);
const hashed = await this.cryptoRepository.hashBcrypt(dto.newPinCode, SALT_ROUNDS);
await this.userRepository.update(auth.user.id, { pinCode: hashed });
}
private validatePinCode(
user: { pinCode: string | null; password: string | null },
dto: { pinCode?: string; password?: string },
) {
if (!user.pinCode) {
throw new BadRequestException('User does not have a PIN code');
}
if (dto.password) {
if (!this.validateSecret(dto.password, user.password)) {
throw new BadRequestException('Wrong password');
}
} else if (dto.pinCode) {
if (!this.validateSecret(dto.pinCode, user.pinCode)) {
throw new BadRequestException('Wrong PIN code');
}
} else {
throw new BadRequestException('Either password or pinCode is required');
}
}
async adminSignUp(dto: SignUpDto): Promise<UserAdminResponseDto> {
const admin = await this.createUser({
isAdmin: true,View on GitHub (pinned to f48d4b3321)