Skip to main content

Authentication

Base path: /api/auth

POST /auth/login

Log in with email and password.

Auth: None

Request body:

FieldTypeRequiredDescription
emailstringYesEmail address
passwordstringYesPassword
mobilebooleanNoIf true, a 30-day refresh token is returned

Response (200):

{
"token": "jwt-access-token",
"refresh_token": "only-when-mobile-true",
"user": {
"id": 1,
"name": "Admin",
"email": "admin@salon.nl",
"role": "admin",
"require_password_change": false
}
}

Access tokens expire after 8 hours. When mobile: true is sent, a refresh token with 30-day lifetime is also returned and stored in the refresh_tokens table.

Errors: 401 — Invalid credentials


POST /auth/logout

Log out and revoke refresh/device token.

Auth: Required (any role)

Request body:

FieldTypeDescription
refresh_tokenstringRefresh token to revoke
device_tokenstringPush device token to remove

Both fields are optional; send whichever tokens should be revoked.


POST /auth/refresh

Renew access token via refresh token. The old refresh token is deleted and a new one is issued (token rotation).

Auth: None

Request body:

FieldTypeRequired
refresh_tokenstringYes

Response (200):

{
"token": "new-access-token",
"refresh_token": "new-refresh-token",
"user": {
"id": 1,
"name": "Admin",
"email": "admin@salon.nl",
"role": "admin",
"require_password_change": false
}
}

Errors: 401 — Invalid or expired refresh token


POST /auth/register

Create a new user (admin only). Sends a welcome email with a temporary password. The new user will have require_password_change: true.

Auth: Admin

Request body:

FieldTypeRequiredDefault
emailstringYes
namestringYes
rolestringNostaff

Response (200): { id, email, role, name }

Errors: 409 — Email address already registered


POST /auth/forgot-password

Send a password reset email. Always returns 200 (prevents email enumeration). Reset links expire after 1 hour. A 60-second throttle prevents duplicate requests.

Auth: None

Request body: { "email": "user@example.com" }


POST /auth/reset-password

Reset password with token from the reset email.

Auth: None

Request body:

FieldTypeRequired
emailstringYes
tokenstringYes
passwordstring (min 8)Yes

On success, a password-changed confirmation email is sent and all pending reset tokens for the email are deleted.

Errors: 400 — Invalid or expired token


POST /auth/change-password

Change password for a logged-in user. Requires the current password and sends a confirmation email on success.

Auth: Required (any role, no middleware — validated by email/current password match)

Request body:

FieldTypeRequired
emailstringYes
currentPasswordstringYes
newPasswordstring (min 8)Yes
confirmPasswordstring (min 8)Yes

Errors: 400 — Passwords do not match; 401 — Wrong current password


GET /auth/users

Get all users (admin only).

Auth: Admin

Response: Array of { id, name, email, role } objects.


POST /auth/device

Register a push notification device token. If the token already exists for another user it is reassigned.

Auth: Required

Request body:

FieldTypeRequired
tokenstring (max 512)Yes
platformios / android / webYes

DELETE /auth/device

Remove a device token.

Auth: Required

Request body: { "token": "device-token" }


GET /auth/setup-status

Check if initial setup is needed. Returns needsSetup: true when the database has zero user accounts.

Auth: None

Response:

{ "needsSetup": true }

POST /auth/setup

Initial setup endpoint. Only works when zero user accounts exist.

Auth: None

Request body:

FieldTypeRequiredDescription
admin_namestringYesAdmin display name
admin_emailstringYesAdmin email
admin_passwordstring (min 8)YesAdmin password
salon_namestringYesSalon name
salon_emailstringNoSalon contact email
salon_phonestringNoSalon phone number
salon_addressstringNoSalon address
opening_hoursarrayNoArray of { day_of_week, open_time, close_time, is_closed }

Response (201):

{
"token": "jwt-access-token",
"user": { "id": 1, "name": "Admin", "role": "admin" }
}

Errors: 400 — Setup already completed (users exist)

info

The setup wizard in the web app calls this endpoint. Once setup is complete, the endpoint rejects all further calls.