
# Update the identity settings

`PATCH /v1/identity-settings` · permission `identity-settings:write`

Updates the environment's username settings with a JSON merge patch (`application/merge-patch+json`, RFC 7396). `If-Match` is required: missing gives `428`, a stale version gives `412`. If the settings were never written the version is `"0"` and the first write creates the row. A patch that changes nothing writes nothing and keeps the version.

## Request

| Field | Type | Required | Description |
|---|---|---|---|
| `If-Match` | header | yes | The `ETag` of the settings as last read (`"0"` if never written). Missing: `428`; stale: `412`. |
| `username_identifier` | string | no | `required`, `optional` or `off`. Cannot be `null`. |
| `username_min_length` | integer | no | 3-64, not greater than `username_max_length`. Cannot be `null`. |
| `username_max_length` | integer | no | 3-64, not less than `username_min_length`. Cannot be `null`. |
| `reserved_usernames` | string[] \| null | no | At most 1000 names; replaces the whole list (RFC 7396 does not merge arrays). `null` or `[]` clears it. Each entry is normalized, duplicates are dropped and the list is stored sorted. |
| `verification_policy` | string | no | `before_sign_in` (default) or `after_sign_in`. `after_sign_in` stops the unverified-account cleanup. Cannot be `null`. |
| `unverified_account_ttl_days` | integer | no | 1-30 (default 7): days an unverified sign-up is kept in a `before_sign_in` environment before it is deleted. Cannot be `null`. |

The username settings apply to later writes only; existing users are never changed by them. The verification settings also act on existing sign-ups (see below).

## Enforcement rules

- `off` (default): a request that writes a username (`POST /v1/users`, `PATCH /v1/users/{id}`) fails with `422` and code `identifier_type_disabled` (pointer `/username`). Clearing an existing username stays allowed, and a user patch that does not touch the username is not checked again. Sign-in does not resolve accounts by username.
- `optional`: a username may be set. `required`: creating a user through the management API without a username fails with `422` (`required`), and a management patch cannot clear it. Asking for the username on the sign-up form is up to the hosted login; social and just-in-time creation never ask for it.
- Length: a username shorter than `username_min_length` or longer than `username_max_length` fails with `422` (`invalid`).
- Reserved names: a username whose normalized form is in `reserved_usernames` fails with `422` and code `username_reserved`. An invalid list entry fails with `422` and pointer `/reserved_usernames/<i>`.
- Existing users are not changed when the setting changes; the rules apply to later writes only.

## Unverified-account cleanup

`verification_policy` and `unverified_account_ttl_days` control the automatic cleanup of sign-ups that were never verified. The cleanup only runs while the email address (or the phone number) is a required identifier, which is the default for email.

- `before_sign_in` (default): a user created by sign-up cannot receive tokens before verifying the required identifier. A sign-up user who, `unverified_account_ttl_days` days (1-30, default 7) after signing up, still has no verified email address or phone number, has never received a token and has no social or enterprise (SAML/OIDC) identity is permanently deleted. The cleanup runs periodically (hourly by default).
- `after_sign_in`: users receive tokens before verifying (`email_verified: false`); the cleanup does not run and no user is deleted.
- Never deleted: users created through the management API, import, SCIM, an invitation or just-in-time federation, blocked users and users whose deletion has already started. Only sign-ups created after the cleanup started in the environment are eligible.
- Deletion cannot be undone: the user is hard-deleted, a tombstone with reason `unverified_expired` is kept and the `user.deleted` event is published.
- A change of policy or period takes effect at the next run. Lowering `unverified_account_ttl_days` makes older unverified sign-ups eligible at once; switching from `after_sign_in` back to `before_sign_in` deletes expired unverified sign-ups at the next run.

## Response

`200 OK` with the updated settings and the new `ETag`.

```json
{
  "username_identifier": "optional",
  "username_min_length": 3,
  "username_max_length": 32,
  "reserved_usernames": ["admin", "root", "support"],
  "verification_policy": "before_sign_in",
  "unverified_account_ttl_days": 7,
  "updated_at": "2026-10-05T09:30:00.123456Z"
}
```

## Errors

| HTTP | code | When |
|---|---|---|
| 400 | `unknown_field` | The patch contains an unknown or read-only field, or is not a JSON object (`malformed_request`). |
| 412 | `precondition_failed` | `If-Match` does not match the current version. |
| 415 | `unsupported_media_type` | The content type is not `application/merge-patch+json`. |
| 422 | `validation_failed` | A value is invalid, `min` is greater than `max`, or a field that cannot be `null` was set to `null` (`code: invalid`). |
| 428 | `precondition_required` | The `If-Match` header is missing. |
| 401 | `unauthenticated` | The credential is missing, invalid or revoked. |
| 403 | `insufficient_permissions` | The credential lacks the required permission. |
| 429 | `rate_limited` | The rate limit was exceeded; retry after the `Retry-After` period. |
| 503 | `service_unavailable` | A dependency is temporarily unavailable; retry after the `Retry-After` period. |

## Example

```bash
curl -X PATCH https://api.eu.keenauth.com/v1/identity-settings \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/merge-patch+json" \
  -H 'If-Match: "0"' \
  -d '{"username_identifier":"optional","username_max_length":32,"reserved_usernames":["admin","root","support"]}'
```
