Skip to content
innovorder
⌘K

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

  1. 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 is customer.passwordExpiresAt being in the past.
  2. 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.
  3. Set the new password with PUT /customers/{customerId}/reset_password, authenticated with the same Bearer token obtained at login.
  4. Continue the session. On success, passwordExpiresAt is 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

json
{
  "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.

PropertyTypeExampleDescription
usernamestring"john.doe@company.com"The username value.
passwordstring"12345"The password value.
brandIdinteger496Identifier of the brand.
grant_typestring"password"The grant type value.

Response

json
{
  "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.

PropertyTypeExampleDescription
statusinteger201HTTP status code returned by the API.
codestring"token_created"Machine-readable application code for the result.
messagestring"You have successfully logged in."Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.accessTokenstring"eyJhbGciOiJIUzI1..."The access token value.
data.tokenTypestring"Bearer"The token type value.
data.customerobject{…}Object containing customer fields.
data.customer.customerIdinteger5337578Identifier of the customer.
data.customer.brandIdinteger496Identifier of the brand.
data.customer.emailstring"john.doe@company.com"Email address.
data.customer.firstNamestring"John"The first name value.
data.customer.lastNamestring"Doe"The last name value.
data.customer.badgeNumberstring"0012345"The badge number value.
data.customer.passwordExpiresAtstring"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.

401 response on any customer endpoint while the password is expired
{
    "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

NameTypeRequiredDescription
customerIdintegerYesThe unique identifier of the customer (must match the authenticated customer).

Request Body

json
{
  "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.

PropertyTypeExampleDescription
customerIdinteger5337578Identifier of the customer.
newPasswordstring"MyNewPassw0rd!"The new password value.

Response

json
{
  "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.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"forgottenPassword_retrieve"Machine-readable application code for the result.
messagestring"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:

securedPassword regex
^(?=.*[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

StatusCodeMeaning
401login_failedWrong username/password combination at login.
401password_expiredReturned by any other customer endpoint while the password change is pending. Treat as "go to the password-change screen", not as a login failure.
400password_not_expiredThe password is not expired: the flow is not applicable or has already been completed. Calling the reset endpoint twice returns this.
403access_deniedThe token does not belong to the target customerId (a customer can only reset their own password).
401token_expiredThe Bearer token itself has expired - send the customer back to the login screen.