Customer identity
First Login - Mandatory Password Change
Brands can require customers created or imported with default credentials to set a personal password on their first login. This page describes how to detect that state after login and how to complete the mandatory password change.
Which accounts are concerned?
The flow applies to brands using the option FORCE_DEFAULT_CREDENTIALS_CHANGE (email and password must be updated) or FORCE_DEFAULT_PASSWORD_CHANGE_ONLY (password only) - typically corporate catering brands whose guests are imported with default credentials. Customers created or imported while one of these options is active get a passwordExpiresAt date that is already in the past.
How the Flow Works
- Login with the default password. The login succeeds (
201,token_created) and returns the access token plus the customer payload. The "must change password" signal iscustomer.passwordExpiresAtbeing in the past. - Detect the expired password. While it is expired, every other customer-scoped endpoint returns
401 password_expired. Send the customer to your password-change screen. - Set the new password with
PUT /customers/{customerId}/reset_password, authenticated with the same Bearer token obtained at login. - Continue the session. On success,
passwordExpiresAtis cleared and the existing token remains valid - no re-login is needed.
Step 1 - Login with the Default Password
This is not a login error
Logging in with the default password succeeds and issues a valid token. Do not treat the response as a failure: inspect customer.passwordExpiresAt - a date in the past means "password change required". It is null for accounts that do not need to change their password.
POST/oauth/loginCustomer Login
Authenticate the customer with their username (email or badge), the default password, and the brandId. With correct credentials the API responds 201 even when the password is expired.
Request Body
{
"username": "john.doe@company.com",
"password": "12345",
"brandId": 496,
"grant_type": "password"
}Request Body Properties
Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.
| Property | Type | Example | Description |
|---|---|---|---|
| username | string | "john.doe@company.com" | The username value. |
| password | string | "12345" | The password value. |
| brandId | integer | 496 | Identifier of the brand. |
| grant_type | string | "password" | The grant type value. |
Response
{
"status": 201,
"code": "token_created",
"message": "You have successfully logged in.",
"data": {
"accessToken": "eyJhbGciOiJIUzI1...",
"tokenType": "Bearer",
"customer": {
"customerId": 5337578,
"brandId": 496,
"email": "john.doe@company.com",
"firstName": "John",
"lastName": "Doe",
"badgeNumber": "0012345",
"passwordExpiresAt": "2026-01-05T09:00:00.000Z"
}
}
}Response Properties
Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 201 | HTTP status code returned by the API. |
| code | string | "token_created" | Machine-readable application code for the result. |
| message | string | "You have successfully logged in." | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.accessToken | string | "eyJhbGciOiJIUzI1..." | The access token value. |
| data.tokenType | string | "Bearer" | The token type value. |
| data.customer | object | {…} | Object containing customer fields. |
| data.customer.customerId | integer | 5337578 | Identifier of the customer. |
| data.customer.brandId | integer | 496 | Identifier of the brand. |
| data.customer.email | string | "john.doe@company.com" | Email address. |
| data.customer.firstName | string | "John" | The first name value. |
| data.customer.lastName | string | "Doe" | The last name value. |
| data.customer.badgeNumber | string | "0012345" | The badge number value. |
| data.customer.passwordExpiresAt | string | "2026-01-05T09:00:00.000Z" | Date or timestamp for password expires. |
Step 2 - Expired Password Blocks Every Other Call
While passwordExpiresAt is in the past, every other customer-scoped endpoint (profile, balance, ordering, ...) responds HTTP 401 with code password_expired. Retrying the login without changing the password keeps succeeding (201) and keeps returning the past passwordExpiresAt.
{
"status": 401,
"code": "password_expired",
"message": "Your password has expired."
}Step 3 - Set the New Password
Call the reset endpoint with the same Bearer token obtained at login - this endpoint explicitly accepts tokens of customers whose password is expired. No old password is required: the proof is that the account is in the expired state.
PUT/customers/{customerId}/reset_passwordFirst-Login Password Change
Set the new password. Authenticated with the Bearer token from login. Both body fields are required. If the password is not actually expired, the endpoint returns 400 password_not_expired.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| customerId | integer | Yes | The unique identifier of the customer (must match the authenticated customer). |
Request Body
{
"customerId": 5337578,
"newPassword": "MyNewPassw0rd!"
}Request Body Properties
Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.
| Property | Type | Example | Description |
|---|---|---|---|
| customerId | integer | 5337578 | Identifier of the customer. |
| newPassword | string | "MyNewPassw0rd!" | The new password value. |
Response
{
"status": 200,
"code": "forgottenPassword_retrieve",
"message": "Password updated successfully."
}Response Properties
Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "forgottenPassword_retrieve" | Machine-readable application code for the result. |
| message | string | "Password updated successfully." | Human-readable result message. Do not use this value for program logic. |
On success (200) the server stores the new password and clears passwordExpiresAt. The token issued at login remains valid: keep the session and continue - all customer endpoints work normally from this point, and subsequent logins with the new password no longer trigger the flow.
Variant: FORCE_DEFAULT_CREDENTIALS_CHANGE
With FORCE_DEFAULT_CREDENTIALS_CHANGE the customer must also replace the default email: call PUT /customers/{customerId} with the new email (see Manage Customers). With FORCE_DEFAULT_PASSWORD_CHANGE_ONLY, only the password change above is required.
Password Requirements
The new password must satisfy the following policy:
- Minimum 12 characters (maximum 50)
- At least one lowercase letter
- At least one uppercase letter
- At least one digit
- At least one special character
This is the API's securedPassword rule:
^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()\[\]{}€£?_;.:,\-+='"~<>]).{12,50}$Validate the password client-side
The reset endpoint itself does not re-validate password strength server-side today: the rule is enforced at customer signup when the brand option ADVANCED_SECURITY_RULES is active, and client-side in the Innovorder ordering app. Integrators building their own client for this flow must validate the new password against the rules above before submitting - do not rely on this endpoint to reject a weak password.
Error Reference
| Status | Code | Meaning |
|---|---|---|
| 401 | login_failed | Wrong username/password combination at login. |
| 401 | password_expired | Returned by any other customer endpoint while the password change is pending. Treat as "go to the password-change screen", not as a login failure. |
| 400 | password_not_expired | The password is not expired: the flow is not applicable or has already been completed. Calling the reset endpoint twice returns this. |
| 403 | access_denied | The token does not belong to the target customerId (a customer can only reset their own password). |
| 401 | token_expired | The Bearer token itself has expired - send the customer back to the login screen. |