# 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/login` - Customer 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.

| 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

```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.

| 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`.

**401 response on any customer endpoint while the password is expired**

```json
{
    "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_password` - First-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

```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.

| Property | Type | Example | Description |
| --- | --- | --- | --- |
| customerId | integer | 5337578 | Identifier of the customer. |
| newPassword | string | "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.

| 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](https://developers.innovorder.io/docs/customers/customers-manage.md)). 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**

```text
^(?=.*[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. |
